Concatenamento dei metodi: perché è una buona pratica o no?


151

Il concatenamento dei metodi è la pratica dei metodi oggetto che restituiscono l'oggetto stesso affinché il risultato venga chiamato per un altro metodo. Come questo:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

Questa sembra essere considerata una buona pratica, poiché produce codice leggibile o "interfaccia fluente". Tuttavia, a me sembra invece spezzare l'oggetto che chiama la notazione implicita dall'orientamento dell'oggetto stesso - il codice risultante non rappresenta azioni che eseguono il risultato di un metodo precedente, che è come generalmente dovrebbe funzionare il codice orientato agli oggetti:

participant.getSchedule('monday').saveTo('monnday.file')

Questa differenza riesce a creare due significati diversi per la notazione a punti di "chiamare l'oggetto risultante": Nel contesto del concatenamento, l'esempio sopra si legge come salvare l' oggetto partecipante , anche se l'esempio è effettivamente destinato a salvare la pianificazione oggetto ricevuto da getSchedule.

Capisco che la differenza qui è se ci si dovrebbe aspettare che il metodo chiamato restituisca qualcosa o no (nel qual caso restituirebbe l'oggetto chiamato stesso per il concatenamento). Ma questi due casi non sono distinguibili dalla notazione stessa, ma solo dalla semantica dei metodi chiamati. Quando il concatenamento di metodo non viene utilizzato, posso sempre sapere che una chiamata di metodo opera su qualcosa correlato al risultato della chiamata precedente - con il concatenamento, questa ipotesi si interrompe e devo elaborare semanticamente l'intera catena per capire quale sia l'oggetto reale chiamato è davvero. Per esempio:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

Qui le ultime due chiamate al metodo si riferiscono al risultato di getSocialStream, mentre quelle precedenti si riferiscono al partecipante. Forse è una cattiva pratica scrivere effettivamente catene in cui il contesto cambia (è?), Ma anche allora dovrai controllare costantemente se le catene di punti che sembrano simili sono effettivamente mantenute nello stesso contesto o funzionano solo sul risultato .

A me sembra che mentre il concatenamento del metodo produce superficialmente codice leggibile, sovraccaricare il significato della notazione a punti provoca solo maggiore confusione. Dato che non mi considero un guru della programmazione, presumo che la colpa sia mia. Quindi: cosa mi sto perdendo? Capisco il metodo concatenamento in qualche modo sbagliato? Ci sono alcuni casi in cui il concatenamento dei metodi è particolarmente buono o altri in cui è particolarmente negativo?

Sidenote: capisco che questa domanda potrebbe essere letta come una dichiarazione di opinione mascherata da una domanda. Tuttavia, non lo è - voglio sinceramente capire perché il concatenamento è considerato una buona pratica e dove sbaglio nel pensare che rompa la notazione intrinseca orientata agli oggetti.


Sembra che il metodo del concatenamento sia almeno un modo per scrivere codice intelligente in Java. Anche se non tutti sono d'accordo ...
Mercer Traieste,

Chiamano anche questi metodi "fluenti" o un'interfaccia "fluente". Potresti voler aggiornare il tuo titolo per usare questo termine.
S.Lott

4
In un'altra discussione SO è stato detto che un'interfaccia fluida è un concetto più ampio che ha a che fare con la leggibilità del codice, e il concatenamento dei metodi è solo un modo per puntare a questo. Sono strettamente correlati, tuttavia, quindi ho aggiunto il tag e fatto riferimento a interfacce fluide nel testo - penso che siano sufficienti.
Ilari Kajaste,

14
Il modo in cui sono arrivato a pensare a questo, è che il metodo del concatenamento è, in effetti, un trucco per ottenere una caratteristica mancante nella sintassi del linguaggio. Non sarebbe davvero necessario se ci fosse una notazione alternativa integrata .che ignorerebbe qualsiasi valore restituito e invocherebbe sempre qualsiasi metodo incatenato usando lo stesso oggetto.
Ilari Kajaste,

È un grande linguaggio di programmazione, ma come tutti gli ottimi strumenti viene abusato.
Martin Spamer,

Risposte:


74

Sono d'accordo che questo è soggettivo. Per la maggior parte evito il concatenamento dei metodi, ma recentemente ho anche trovato un caso in cui era la cosa giusta: avevo un metodo che accettava qualcosa come 10 parametri e ne avevo bisogno di più, ma per la maggior parte del tempo dovevi solo specificare un pochi. Con le sostituzioni questo è diventato molto ingombrante molto velocemente. Invece ho optato per l'approccio concatenato:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

L'approccio del metodo di concatenamento è stato facoltativo, ma ha semplificato la scrittura del codice (soprattutto con IntelliSense). Ricorda che questo è un caso isolato, e non è una pratica generale nel mio codice.

Il punto è che, nel 99% dei casi, puoi probabilmente fare altrettanto o anche meglio senza concatenare il metodo. Ma c'è l'1% in cui questo è l'approccio migliore.


4
IMO, il modo migliore per utilizzare il concatenamento dei metodi in questo scenario è creare un oggetto parametro da passare alla funzione, ad esempio P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Hai il vantaggio di poter riutilizzare questo oggetto parametro in altre chiamate, il che è un vantaggio!
pedromanoel

20
Solo il mio centesimo di contributo sugli schemi, il metodo di fabbrica di solito ha solo un punto di creazione e i prodotti sono una scelta statica basata sui parametri del metodo di fabbrica. La creazione della catena assomiglia di più a un modello Builder in cui chiamate diversi metodi per ottenere un risultato, i metodi possono essere opzionali come nel concatenamento dei metodi , possiamo avere qualcosa come PizzaBuilder.AddSauce().AddDough().AddTopping()più riferimenti qui
Marco Medrano

3
Il concatenamento del metodo (come indicato nella domanda originale) è considerato negativo quando viola la legge di Demeter . Vedi: ifacethoughts.net/2006/03/07/… La risposta fornita qui segue infatti quella legge in quanto è un "modello di costruzione".
Angel O'Sphere,

2
@Marco Medrano L'esempio di PizzaBuilder mi ha sempre infastidito da quando l'ho letto in JavaWorld anni fa. Sento che dovrei aggiungere salsa alla mia pizza, non al mio chef.
Breandán Dalton,

1
So cosa intendi, Vilx. Eppure quando ho letto list.add(someItem), ho letto che come "questo codice è l'aggiunta someItemal listoggetto". così quando leggo PizzaBuilder.AddSauce(), leggo questo naturalmente come "questo codice sta aggiungendo salsa PizzaBuilderall'oggetto". In altre parole, vedo l'orchestrator (quello che sta facendo l'aggiunta) come il metodo in cui è incorporato il codice list.add(someItem)o PizzaBuilder.addSauce(). Ma penso solo che l'esempio di PizzaBuilder sia un po 'artificiale. Il tuo esempio di MyObject.SpecifySomeParameter(asdasd)funziona benissimo per me.
Breandán Dalton,

78

Solo i miei 2 centesimi;

Il concatenamento dei metodi rende difficile il debug: - Non è possibile inserire il punto di interruzione in un punto conciso in modo da poter mettere in pausa il programma esattamente dove lo si desidera - Se uno di questi metodi genera un'eccezione e si ottiene un numero di riga, non si ha idea quale metodo nella "catena" ha causato il problema.

Penso che sia generalmente buona norma scrivere sempre righe molto brevi e concise. Ogni linea dovrebbe fare solo una chiamata di metodo. Preferisci più linee a linee più lunghe.

EDIT: il commento menziona che il concatenamento del metodo e l'interruzione di linea sono separati. Questo è vero. A seconda del debugger, tuttavia, potrebbe essere o non essere possibile posizionare un punto di interruzione nel mezzo di un'istruzione. Anche se puoi, usare linee separate con variabili intermedie ti dà molta più flessibilità e un sacco di valori che puoi esaminare nella finestra di controllo che aiuta il processo di debug.


4
L'uso di newline e il concatenamento dei metodi non sono indipendenti? È possibile concatenare ogni chiamata su una nuova riga come da @ Vilx- answer e in genere è possibile inserire più istruzioni separate sulla stessa linea (ad es. Utilizzando punti e virgola in Java).
Brabster,

2
Questa risposta è pienamente giustificata, ma mostra solo una debolezza presente in tutti i debugger che conosco e non è specificamente correlata alla domanda.
masterxilo,

1
@Brabster. Possono essere separati per alcuni debugger, tuttavia, avere chiamate separate con variabili intermedie ti dà ancora una ricchezza molto più grande di informazioni mentre indaghi sui bug.
RAY

4
+1, in nessun momento sai cosa ha restituito ciascun metodo durante il debug di una catena di metodi. Il modello della catena del metodo è un hack. È il peggior incubo di un programmatore di manutenzione.
Lee Kowalkowski il

1
Non sono neanche un grande fan del concatenamento, ma perché non mettere semplicemente il punto di interruzione all'interno delle definizioni del metodo?
Pankaj Sharma,

39

Personalmente, preferisco concatenare metodi che agiscono solo sull'oggetto originale, ad esempio impostando più proprietà o chiamando metodi di tipo utility.

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

Non lo uso quando uno o più dei metodi concatenati restituirebbero qualsiasi oggetto diverso da pippo nel mio esempio. Mentre sintatticamente puoi concatenare qualsiasi cosa fintanto che stai usando l'API corretta per quell'oggetto nella catena, cambiare gli oggetti IMHO rende le cose meno leggibili e può davvero confondere se le API per i diversi oggetti hanno delle somiglianze. Se esegui una chiamata di metodo molto comune alla fine ( .toString(),.print() , a prescindere) quale oggetto stai in ultima analisi, che agiscono su? Qualcuno che legge casualmente il codice potrebbe non accorgersi che sarebbe un oggetto implicitamente restituito nella catena piuttosto che il riferimento originale.

Concatenare oggetti diversi può anche portare a errori null imprevisti. Nei miei esempi, supponendo che foo sia valido, tutte le chiamate al metodo sono "sicure" (ad esempio, valide per foo). Nell'esempio del PO:

participant.getSchedule('monday').saveTo('monnday.file')

... non esiste alcuna garanzia (in quanto sviluppatore esterno che osserva il codice) che getSchedule restituirà effettivamente un oggetto di pianificazione valido, non nullo. Inoltre, il debug di questo stile di codice è spesso molto più difficile poiché molti IDE non valuteranno la chiamata del metodo al momento del debug come oggetto che è possibile controllare. IMO, ogni volta che potresti aver bisogno di un oggetto da ispezionare per scopi di debug, preferisco averlo in una variabile esplicita.


Se c'è possibilità che un Participantnon hai valida Scheduleallora getSchedulemetodo è progettato per restituire un Maybe(of Schedule)tipo e saveTometodo è progettato per accettare un Maybetipo.
Lightman il

24

Martin Fowler ha una buona discussione qui:

Metodo di concatenamento

Quando usarlo

Il metodo di concatenamento può aggiungere molto alla leggibilità di un DSL interno e di conseguenza è diventato quasi un sinonimo di DSL interno in alcune menti. Metodo Il concatenamento è il migliore, tuttavia, quando viene utilizzato insieme ad altre combinazioni di funzioni.

Metodo Il concatenamento è particolarmente efficace con grammatiche come parent :: = (this | that) *. L'uso di metodi diversi fornisce un modo leggibile di vedere quale argomento verrà dopo. Allo stesso modo gli argomenti opzionali possono essere facilmente ignorati con Method Chaining. Un elenco di clausole obbligatorie, come parent :: = first second, non funziona così bene con il modulo di base, sebbene possa essere supportato bene utilizzando interfacce progressive. Il più delle volte preferirei la funzione nidificata per quel caso.

Il problema più grande per il metodo di concatenamento è il problema di finitura. Mentre ci sono soluzioni alternative, di solito se ti imbatti in questo è meglio usare una funzione nidificata. La funzione annidata è anche una scelta migliore se si entra in un pasticcio con le variabili di contesto.


2
Il link è morto :(
Qw3ry,

Cosa intendi con DSL? Linguaggio specifico del dominio
Sören

@ Sören: Fowler si riferisce a linguaggi specifici del dominio.
Dirk Vollmar,

21

Secondo me, il concatenamento dei metodi è un po 'una novità. Certo, sembra bello ma non vedo alcun vantaggio reale in esso.

Com'è:

someList.addObject("str1").addObject("str2").addObject("str3")

meglio di:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

L'eccezione potrebbe essere quando addObject () restituisce un nuovo oggetto, nel qual caso il codice unchained potrebbe essere un po 'più ingombrante come:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

9
È più conciso, poiché eviti due delle parti 'someList' anche nel tuo primo esempio e finisci con una riga anziché tre. Ora, se questo è effettivamente buono o cattivo dipende da cose diverse e forse è una questione di gusti.
Fabian Steeg,

26
Il vero vantaggio di avere "someList" solo una volta è che è molto più facile assegnargli un nome più lungo e più descrittivo. Ogni volta che un nome deve apparire più volte in rapida successione, si tende a renderlo breve (per ridurre la ripetizione e migliorare la leggibilità), il che lo rende meno descrittivo, danneggiando la leggibilità.
Chris Dodd,

Un buon punto sulla leggibilità e sui principi DRY con l'avvertenza che il concatenamento del metodo impedisce l'introspezione del valore di ritorno di un determinato metodo e / o implica una presunzione di elenchi / insiemi vuoti e valori non nulli da ogni metodo nella catena (un errore che causa molti NPE nei sistemi che devo eseguire il debug / correggere spesso).
Darrell Teague,

@ChrisDodd Mai sentito parlare del completamento automatico?
inf3rno,

1
"il cervello umano è molto bravo a riconoscere la ripetizione del testo se ha la stessa rientranza" - questo è esattamente il problema con la ripetizione: fa sì che il cervello si concentri sul modello ripetitivo. Quindi per leggere E COMPRENDERE il codice devi forzare il tuo cervello a guardare oltre / dietro lo schema ripetuto per vedere cosa sta realmente succedendo, che è nelle differenze tra lo schema ripetitivo che il tuo cervello tende a sorvolare. Questo è il motivo per cui la ripetizione è così negativa per la leggibilità del codice.
Chris Dodd,

7

È pericoloso perché potresti dipendere da più oggetti del previsto, come in questo caso la tua chiamata restituisce un'istanza di un'altra classe:

Farò un esempio:

foodStore è un oggetto composto da molti negozi di alimentari che possiedi. foodstore.getLocalStore () restituisce al parametro un oggetto che contiene informazioni sull'archivio più vicino. getPriceforProduct (qualunque cosa) è un metodo di quell'oggetto.

Quindi quando chiami foodStore.getLocalStore (parametri) .getPriceforProduct (qualunque cosa)

dipendi non solo da FoodStore come credi, ma anche da LocalStore.

Dovrebbe cambiare GetPriceforProduct (qualsiasi cosa), è necessario cambiare non solo FoodStore ma anche la classe che ha chiamato il metodo incatenato.

Dovresti sempre puntare a un accoppiamento lento tra le classi.

Detto questo, personalmente mi piace incatenarli durante la programmazione di Ruby.


7

Molti usano il concatenamento dei metodi come una forma di convenienza piuttosto che avere in mente problemi di leggibilità. Il concatenamento dei metodi è accettabile se comporta l'esecuzione della stessa azione sullo stesso oggetto, ma solo se migliora effettivamente la leggibilità e non solo per scrivere meno codice.

Sfortunatamente molti usano il metodo concatenato secondo gli esempi forniti nella domanda. Sebbene possano ancora essere resi leggibili, sfortunatamente stanno causando un elevato accoppiamento tra più classi, quindi non è desiderabile.


6

Sembra un po 'soggettivo.

Il concatenamento di metodi non è qualcosa di intrinsecamente cattivo o buono.

La leggibilità è la cosa più importante.

(Considera anche che avere un gran numero di metodi concatenati renderà le cose molto fragili se qualcosa cambia)


Potrebbe davvero essere soggettivo, quindi il tag soggettivo. Ciò che spero che le risposte mi mettano in evidenza è in quali casi il concatenamento del metodo sarebbe una buona idea - in questo momento non ne vedo molto, ma penso che questo sia solo il mio fallimento nel capire i punti positivi del concetto, piuttosto che qualcosa di intrinsecamente cattivo nel concatenamento stesso.
Ilari Kajaste,

Non sarebbe intrinsecamente negativo se si traduce in un elevato accoppiamento? Rompere la catena in singole affermazioni non riduce la leggibilità.
Aberrant80,

1
dipende da quanto dogmatico vuoi essere. Se risulta in qualcosa di più leggibile, questo potrebbe essere preferibile in molte situazioni. Il grande problema che ho con questo approccio è che la maggior parte dei metodi su un oggetto restituirà riferimenti all'oggetto stesso, ma spesso il metodo restituirà un riferimento a un oggetto figlio su cui è possibile concatenare più metodi. Una volta che inizi a farlo, diventa molto difficile per un altro programmatore districare ciò che sta succedendo. Inoltre, qualsiasi cambiamento nella funzionalità di un metodo sarà un problema per il debug in una dichiarazione composta di grandi dimensioni.
John Nicholas,

6

Vantaggi del concatenamento
, ovvero dove mi piace usarlo

Un vantaggio del concatenamento che non ho visto menzionato è la possibilità di usarlo durante l'avvio variabile o quando si passa un nuovo oggetto a un metodo, non sono sicuro che si tratti di una cattiva pratica o meno.

So che questo è un esempio inventato, ma dire che hai le seguenti classi

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

E dire che non si ha accesso alla classe base, o Dire che i valori predefiniti sono dinamici, in base al tempo, ecc. Sì, è possibile creare un'istanza, quindi modificare i valori ma che possono diventare ingombranti, soprattutto se si sta semplicemente passando i valori di un metodo:

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

Ma non è solo molto più facile da leggere:

  PrintLocation(New HomeLocation().X(1337))

Oppure, che dire di un membro della classe?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

vs

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

Questo è il modo in cui ho usato il concatenamento e in genere i miei metodi sono solo per la configurazione, quindi sono lunghi solo 2 righe, quindi imposta un valore Return Me. Per noi ha ripulito enormi righe molto difficili da leggere e comprendere il codice in una riga che si legge come una frase. qualcosa di simile a

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

Vs Something like

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

Detrimento Di Concatenamento
cioè, dove non mi piace usarlo

Non uso il concatenamento quando ci sono molti parametri da passare alle routine, principalmente perché le linee diventano molto lunghe e, come detto dall'OP, può diventare confuso quando si chiamano routine ad altre classi per passare a una delle i metodi di concatenamento.

C'è anche la preoccupazione che una routine restituisca dati non validi, finora ho usato il concatenamento solo quando sto restituendo la stessa istanza chiamata. Come è stato sottolineato se si fa una catena tra le classi si rende più difficile il debug (quale ha restituito null?) E si può aumentare l'accoppiamento delle dipendenze tra le classi.

Conclusione

Come tutto nella vita e nella programmazione, il concatenamento non è né buono né cattivo se si può evitare il male, quindi il concatenamento può essere un grande vantaggio.

Provo a seguire queste regole.

  1. Cerca di non incatenare tra le classi
  2. Crea routine specifiche per il concatenamento
  3. Fai solo UNA cosa in una routine concatenata
  4. Usalo quando migliora la leggibilità
  5. Usalo quando semplifica il codice

6

Il concatenamento dei metodi può consentire la progettazione diretta di DSL avanzati in Java. In sostanza, puoi modellare almeno questi tipi di regole DSL:

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

Queste regole possono essere implementate usando queste interfacce

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

Con queste semplici regole, puoi implementare DSL complessi come SQL direttamente in Java, come è fatto da jOOQ , una libreria che ho creato. Vedi un esempio SQL piuttosto complesso preso dal mio blog qui:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

Un altro bell'esempio è jRTF , un piccolo DSL progettato per cerare documenti RTF direttamente in Java. Un esempio:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );

@ user877329: Sì, può essere utilizzato praticamente in qualsiasi linguaggio di programmazione orientato agli oggetti che conosce qualcosa come interfacce e polimorfismo dei sottotipi
Lukas Eder,

4

Il concatenamento di metodi può essere semplicemente una novità per la maggior parte dei casi, ma penso che abbia il suo posto. Un esempio potrebbe essere trovato nell'uso del Record attivo di CodeIgniter :

$this->db->select('something')->from('table')->where('id', $id);

Sembra molto più pulito (e ha più senso, secondo me) di:

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

È davvero soggettivo; Ognuno ha la propria opinione.


Questo è un esempio di un'interfaccia fluida con concatenamento di metodi, quindi qui UseCase è leggermente diverso. Non stai solo concatenando, ma stai creando un linguaggio specifico del dominio interno che si legge facilmente. Su un sidenote, ActiveRecord di CI non è un ActiveRecord.
Gordon,

3

Penso che l'errore principale sia pensare che questo sia un approccio orientato agli oggetti in generale, quando in realtà è più un approccio di programmazione funzionale che altro.

Le ragioni principali per cui lo uso sono sia per la leggibilità che per impedire che il mio codice venga inondato da variabili.

Non capisco davvero di cosa stiano parlando gli altri quando dicono che danneggia la leggibilità. È una delle forme di programmazione più concise e coerenti che abbia mai usato.

Anche questo:

. ConvertTextToVoice.LoadText ( "source.txt") ConvertToVoice ( "destination.wav");

è come lo userei di solito. Usarlo per catena x numero di parametri non è il modo in cui lo uso in genere. Se volessi inserire un numero x di parametri in una chiamata al metodo, userei la sintassi params :

public void foo (oggetti params [] articoli)

Trasmetti gli oggetti in base al tipo o usa semplicemente un array o una raccolta di tipi di dati a seconda del tuo caso d'uso.


1
+1 su "l'errore principale è pensare che questo sia un approccio orientato agli oggetti in generale, quando in realtà è più un approccio di programmazione funzionale che altro". Il caso d'uso principale è per manipolazioni senza stato su oggetti (dove invece di modificarne lo stato, si restituisce un nuovo oggetto su cui si continua ad agire). Le domande e le altre risposte del PO mostrano azioni statali che sembrano davvero imbarazzanti con il concatenamento.
OmerB,

Sì, hai ragione, è una manipolazione senza stato, tranne che in genere non creo un nuovo oggetto ma utilizzo l'iniezione di dipendenza per renderlo un servizio disponibile. E sì, i casi d'uso con stato non sono ciò a cui penso sia stato destinato il concatenamento di metodi. L'unica eccezione che vedo è se si inizializza il servizio DI con alcune impostazioni e si ha un qualche tipo di watchdog per monitorare lo stato come un servizio COM di qualche tipo. Solo IMHO.
Shane Thorndike,

2

Sono d'accordo, ho quindi cambiato il modo in cui un'interfaccia fluida è stata implementata nella mia libreria.

Prima:

collection.orderBy("column").limit(10);

Dopo:

collection = collection.orderBy("column").limit(10);

Nell'implementazione "prima" le funzioni hanno modificato l'oggetto e sono terminate return this. Ho modificato l'implementazione per restituire un nuovo oggetto dello stesso tipo .

Il mio ragionamento per questo cambiamento :

  1. Il valore restituito non aveva nulla a che fare con la funzione, era puramente lì per supportare la parte concatenata, avrebbe dovuto essere una funzione nulla secondo OOP.

  2. Anche il concatenamento dei metodi nelle librerie di sistema lo implementa in questo modo (come linq o stringa):

    myText = myText.trim().toUpperCase();
    
  3. L'oggetto originale rimane intatto, consentendo all'utente dell'API di decidere cosa farne. Consente di:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Un'implementazione di copia può essere utilizzato anche per la costruzione di oggetti:

    painting = canvas.withBackground('white').withPenSize(10);
    

    Dove la setBackground(color)funzione cambia l'istanza e non restituisce nulla (come dovrebbe) .

  5. Il comportamento delle funzioni è più prevedibile (vedere i punti 1 e 2).

  6. L'uso di un nome di variabile breve può anche ridurre il disordine del codice, senza forzare un api sul modello.

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

Conclusione: a
mio avviso un'interfaccia fluida che utilizza return thisun'implementazione è semplicemente sbagliata.


1
Ma restituire una nuova istanza per ogni chiamata non creerebbe un certo sovraccarico, soprattutto se si utilizzano oggetti più grandi? Almeno per le lingue non gestite.
Apeiron,

@Apeiron Dal punto di vista delle prestazioni è sicuramente più veloce da return thisgestire o meno. Ritengo che ottenga un'API fluente in un modo "non naturale". (Aggiunto motivo 6: mostrare un'alternativa non fluente, che non ha l'overhead / funzionalità aggiunta)
Bob Fanger il

Sono d'accordo con te. È soprattutto meglio lasciare intatto lo stato sottostante di una DSL e restituire nuovi oggetti ad ogni chiamata di metodo, invece ... Sono curioso: che cos'è quella libreria che hai citato?
Lukas Eder,

1

Il punto totalmente mancato qui è che il concatenamento del metodo consente di ASCIUGARE . È un efficace sostituto del "with" (che è implementato male in alcune lingue).

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

Questo è importante per lo stesso motivo per cui DRY è sempre importante; se A risulta essere un errore e queste operazioni devono essere eseguite su B, è necessario eseguire l'aggiornamento solo in 1 posizione, non in 3.

Pragmaticamente, il vantaggio è piccolo in questo caso. Comunque, un po 'meno digitando, un po' più robusto (DRY), lo prenderò.


14
La ripetizione di un nome di variabile nel codice sorgente non ha nulla a che fare con il principio DRY. DRY afferma che "Ogni pezzo di conoscenza deve avere un'unica, inequivocabile, autorevole rappresentazione all'interno di un sistema", o in altri termini, evitare la ripetizione della conoscenza (non la ripetizione del testo ).
pedromanoel,

4
È sicuramente la ripetizione che viola il secco. La ripetizione di nomi di variabili (inutilmente) causa tutto il male allo stesso modo di altre forme di siccità: crea più dipendenze e più lavoro. Nell'esempio sopra, se rinominiamo A, la versione wet avrà bisogno di 3 modifiche e causerà il debug degli errori se uno dei tre viene perso.
Anfurny,

1
Non riesco a vedere il problema che hai sottolineato in questo esempio, poiché tutte le chiamate al metodo sono vicine tra loro. Inoltre, se si dimentica di modificare il nome di una variabile in una riga, il compilatore restituirà un errore e questo verrà corretto prima di poter eseguire il programma. Inoltre, il nome di una variabile è limitato all'ambito della sua dichiarazione. A meno che questa variabile non sia globale, il che è già una cattiva pratica di programmazione. IMO, DRY non si tratta di scrivere meno, ma di mantenere le cose isolate.
Pedromanoel,

Il "compilatore" potrebbe restituire un errore o forse stai utilizzando PHP o JS e l'interprete potrebbe emettere un errore in fase di esecuzione solo quando si verifica questa condizione.
Anfurny,

1

In genere odio il metodo concatenato perché penso che peggiori la leggibilità. La compattezza è spesso confusa con la leggibilità, ma non sono gli stessi termini. Se fai tutto in una singola istruzione, allora è compatto, ma è meno leggibile (più difficile da seguire) la maggior parte delle volte rispetto a farlo in più istruzioni. Come hai notato, a meno che non sia possibile garantire che il valore di ritorno dei metodi utilizzati sia lo stesso, il concatenamento dei metodi sarà fonte di confusione.

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

vs

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

vs

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

vs

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

Come puoi vedere, vinci quasi nulla, perché devi aggiungere interruzioni di riga alla tua singola istruzione per renderla più leggibile e devi aggiungere un rientro per chiarire che stai parlando di oggetti diversi. Bene, se volessi usare un linguaggio basato sull'identificazione, imparerei Python invece di farlo, per non parlare del fatto che la maggior parte degli IDE rimuoverà il rientro formattando automaticamente il codice.

Penso che l'unico posto in cui questo tipo di concatenamento possa essere utile sia il piping dei flussi nella CLI o il JOIN di più query insieme in SQL. Entrambi hanno un prezzo per più dichiarazioni. Ma se vuoi risolvere problemi complessi, finirai anche con quelli che pagano il prezzo e scrivono il codice in più istruzioni usando variabili o scrivendo script bash e stored procedure o viste.

A partire dalle interpretazioni DRY: "Evita la ripetizione della conoscenza (non la ripetizione del testo)". e "Scrivi di meno, non ripetere nemmeno i testi". Il primo è quello che significa veramente il principio, ma il secondo è un malinteso comune perché molte persone non riescono a comprendere stronzate complicate come "Ogni pezzo di conoscenza deve avere un unico, non ambiguo, rappresentanza autorevole all'interno di un sistema ". Il secondo è la compattezza a tutti i costi, che si rompe in questo scenario, perché peggiora la leggibilità. La prima interpretazione viene interrotta da DDD quando si copia il codice tra contesti limitati, poiché l'accoppiamento lento è più importante in quello scenario.


0

Il bene:

  1. È conciso, ma ti consente di inserirne di più in una singola riga in modo elegante.
  2. A volte puoi evitare l'uso di una variabile, che a volte può essere utile.
  3. Potrebbe funzionare meglio.

Il cattivo:

  1. Stai implementando i ritorni, essenzialmente aggiungendo funzionalità ai metodi sugli oggetti che non fanno realmente parte del loro scopo. Sta restituendo qualcosa che hai già puramente per salvare qualche byte.
  2. Nasconde i cambi di contesto quando una catena conduce a un'altra. Puoi ottenerlo con i getter, tranne che è abbastanza chiaro quando il contesto cambia.
  3. Il concatenamento su più righe sembra brutto, non gioca bene con il rientro e può causare confusione nella gestione di alcuni operatori (specialmente nelle lingue con ASI).
  4. Se vuoi iniziare a restituire qualcos'altro che è utile per un metodo incatenato, avrai probabilmente più difficoltà a risolverlo o a risolvere altri problemi.
  5. Stai scaricando il controllo su un'entità che normalmente non eseguiresti a scopi puramente convenienti, anche in linguaggi strettamente tipizzati, errori che non possono essere sempre rilevati.
  6. Potrebbe andare peggio.

Generale:

Un buon approccio è quello di non utilizzare il concatenamento in generale fino a quando non si presentano situazioni o moduli specifici sarebbero particolarmente adatti ad esso.

Il concatenamento può danneggiare la leggibilità in alcuni casi, in particolare quando si pesa ai punti 1 e 2.

Su accasation può essere usato in modo improprio, come invece di un altro approccio (passando ad esempio un array) o mescolando metodi in modi bizzarri (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).


0

Risposta supponente

Il più grande svantaggio del concatenamento è che può essere difficile per il lettore capire come ogni metodo influisce sull'oggetto originale, se lo fa, e quale tipo restituisce ogni metodo.

Alcune domande:

  • I metodi nella catena restituiscono un nuovo oggetto o lo stesso oggetto è mutato?
  • Tutti i metodi nella catena restituiscono lo stesso tipo?
  • In caso contrario, come viene indicato quando cambia un tipo nella catena?
  • Il valore restituito dall'ultimo metodo può essere scartato in modo sicuro?

Il debugging, nella maggior parte delle lingue, può effettivamente essere più difficile con il concatenamento. Anche se ogni passaggio nella catena è sulla propria linea (che tipo di sconfigge lo scopo del concatenamento), può essere difficile ispezionare il valore restituito dopo ogni passaggio, specialmente per i metodi non mutanti.

I tempi di compilazione possono essere più lenti a seconda della lingua e del compilatore, poiché le espressioni possono essere molto più complesse da risolvere.

Credo che, come in ogni cosa, il concatenamento sia una buona soluzione che può essere utile in alcuni scenari. Dovrebbe essere usato con cautela, comprendendo le implicazioni e limitando il numero di elementi della catena a pochi.


0

Nei linguaggi tipizzati (che mancano autoo equivalenti) questo evita che l'implementatore debba dichiarare i tipi dei risultati intermedi.

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

Per catene più lunghe potresti avere a che fare con diversi tipi intermedi, dovresti dichiararli ciascuno.

Credo che questo approccio sia realmente sviluppato in Java dove a) tutte le chiamate di funzione sono invocazioni di funzioni membro e b) sono richiesti tipi espliciti. Naturalmente c'è un compromesso qui in termini, perdendo alcune spiegazioni, ma in alcune situazioni alcune persone trovano che valga la pena.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.