Methodenverkettung - warum ist es eine gute Praxis oder nicht?


151

Methodenverkettung ist die Praxis von Objektmethoden, die das Objekt selbst zurückgeben, damit das Ergebnis für eine andere Methode aufgerufen wird. So was:

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

Dies scheint eine gute Praxis zu sein, da es lesbaren Code oder eine "fließende Schnittstelle" erzeugt. Für mich scheint es jedoch die Objektaufrufnotation zu brechen, die durch die Objektorientierung selbst impliziert wird - der resultierende Code stellt keine Aktionen dar, die auf das Ergebnis einer vorherigen Methode zurückzuführen sind. So wird im Allgemeinen erwartet, dass objektorientierter Code funktioniert:

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

Dieser Unterschied schafft es, zwei verschiedene Bedeutungen für die Punktnotation "Aufrufen des resultierenden Objekts" zu erzeugen: Im Zusammenhang mit der Verkettung würde das obige Beispiel als Speichern des Teilnehmerobjekts lauten , obwohl das Beispiel tatsächlich den Zeitplan speichern soll Objekt von getSchedule empfangen.

Ich verstehe, dass der Unterschied hier darin besteht, ob von der aufgerufenen Methode erwartet werden sollte, dass sie etwas zurückgibt oder nicht (in diesem Fall würde sie das aufgerufene Objekt selbst zur Verkettung zurückgeben). Diese beiden Fälle unterscheiden sich jedoch nicht von der Notation selbst, sondern nur von der Semantik der aufgerufenen Methoden. Wenn die Methodenverkettung nicht verwendet wird, kann ich immer wissen, dass ein Methodenaufruf mit etwas arbeitet, das mit dem Ergebnis des vorherigen Aufrufs zusammenhängt. Bei der Verkettung bricht diese Annahme, und ich muss die gesamte Kette semantisch verarbeiten, um zu verstehen, was das eigentliche Objekt ist genannt ist wirklich. Beispielsweise:

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

Dort beziehen sich die letzten beiden Methodenaufrufe auf das Ergebnis von getSocialStream, während sich die vorherigen auf den Teilnehmer beziehen. Vielleicht ist es eine schlechte Praxis, tatsächlich Ketten zu schreiben, in denen sich der Kontext ändert (oder?), Aber selbst dann müssen Sie ständig überprüfen, ob Punktketten, die ähnlich aussehen, tatsächlich im selben Kontext bleiben oder nur am Ergebnis arbeiten .

Mir scheint, dass die oberflächliche Verkettung von Methoden zwar lesbaren Code erzeugt, die Überladung der Bedeutung der Punktnotation jedoch nur zu mehr Verwirrung führt. Da ich mich nicht als Programmierguru betrachte, gehe ich davon aus, dass der Fehler bei mir liegt. Also: Was vermisse ich? Verstehe ich die Verkettung von Methoden irgendwie falsch? Gibt es Fälle, in denen die Verkettung von Methoden besonders gut oder besonders schlecht ist?

Nebenbemerkung: Ich verstehe, dass diese Frage als eine als Frage maskierte Meinungsäußerung gelesen werden kann. Es ist jedoch nicht so - ich möchte wirklich verstehen, warum Verkettung als gute Praxis angesehen wird und wo ich falsch denke, dass sie die inhärente objektorientierte Notation bricht.


Es scheint, dass die Verkettung von Methoden mindestens eine Möglichkeit ist, intelligenten Code in Java zu schreiben. Auch wenn nicht jeder zustimmt ..
Mercer Traieste

Sie nennen diese "fließenden" Methoden auch eine "fließende" Schnittstelle. Möglicherweise möchten Sie Ihren Titel aktualisieren, um diesen Begriff zu verwenden.
S.Lott

4
In einer anderen SO-Diskussion wurde gesagt, dass eine fließende Schnittstelle ein größeres Konzept ist, das mit der Lesbarkeit von Code zu tun hat, und die Verkettung von Methoden ist nur eine Möglichkeit, dies zu erreichen. Sie sind jedoch eng miteinander verbunden, daher habe ich das Tag hinzugefügt und auf fließende Schnittstellen im Text verwiesen - ich denke, diese sind ausreichend.
Ilari Kajaste

14
Ich habe mir das so vorgestellt: Die Verkettung von Methoden ist in der Tat ein Hackaround, um eine fehlende Funktion in die Sprachsyntax zu integrieren. Es wäre wirklich nicht nötig, wenn es eine eingebaute alternative Notation .gäbe, die alle mehtod-Rückgabewerte ignoriert und immer verkettete Methoden mit demselben Objekt aufruft.
Ilari Kajaste

Es ist eine großartige Codierungssprache, aber wie alle großartigen Tools wird es missbraucht.
Martin Spamer

Antworten:


74

Ich stimme zu, dass dies subjektiv ist. Zum größten Teil vermeide ich die Verkettung von Methoden, aber kürzlich habe ich auch einen Fall gefunden, in dem es genau das Richtige war - ich hatte eine Methode, die ungefähr 10 Parameter akzeptierte und mehr benötigte, aber für die meiste Zeit musste man nur eine angeben wenige. Mit Overrides wurde dies sehr schnell sehr umständlich. Stattdessen habe ich mich für den Verkettungsansatz entschieden:

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

Der Ansatz der Methodenverkettung war optional, erleichterte jedoch das Schreiben von Code (insbesondere mit IntelliSense). Wohlgemerkt, dass dies ein Einzelfall ist und in meinem Code keine allgemeine Praxis ist.

Der Punkt ist - in 99% der Fälle können Sie wahrscheinlich genauso gut oder sogar besser ohne Methodenverkettung abschneiden. Aber es gibt die 1%, bei denen dies der beste Ansatz ist.


4
IMO, der beste Weg, um die Methodenverkettung in diesem Szenario zu verwenden, besteht darin, ein Parameterobjekt zu erstellen, das an die Funktion übergeben wird, wie z P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Sie haben den Vorteil, dass Sie dieses Parameterobjekt in anderen Aufrufen wiederverwenden können, was ein Plus ist!
Pedromanoel

20
Nur mein Cent Beitrag zu den Mustern, Factory-Methode hat normalerweise nur einen Erstellungspunkt und die Produkte sind eine statische Auswahl basierend auf den Parametern der Factory-Methode. Die Kettenerstellung sieht eher nach einem Builder-Muster aus, bei dem Sie verschiedene Methoden aufrufen, um ein Ergebnis zu erhalten. Die Methoden können optional sein, wie bei der Methodenverkettung . Wir können hier so etwas wie PizzaBuilder.AddSauce().AddDough().AddTopping()mehr Referenzen haben
Marco Medrano

3
Die Verkettung von Methoden (wie in der ursprünglichen Frage dargestellt) wird als schlecht angesehen, wenn sie gegen das Gesetz des Demeters verstößt . Siehe: ifacethoughts.net/2006/03/07/… Die hier gegebene Antwort folgt tatsächlich diesem Gesetz, da es sich um ein "Builder-Muster" handelt.
Angel O'Sphere

2
@Marco Medrano Das PizzaBuilder-Beispiel hat mich immer nervt, seit ich es vor langer Zeit in JavaWorld gelesen habe. Ich denke, ich sollte meiner Pizza Sauce hinzufügen, nicht meinem Koch.
Breandán Dalton

1
Ich weiß was du meinst, Vilx. Aber wenn ich lese list.add(someItem), lese ich das als "dieser Code fügt someItemdem listObjekt hinzu". Wenn ich also lese PizzaBuilder.AddSauce(), lese ich dies natürlich als "dieser Code fügt dem PizzaBuilderObjekt Sauce hinzu ". Mit anderen Worten, sehe ich die Orchestrierung (die man die Zugabe tun) als das Verfahren , in dem der Code list.add(someItem)oder PizzaBuilder.addSauce()eingebettet ist. Aber ich denke nur, dass das PizzaBuilder-Beispiel ein wenig künstlich ist. Ihr Beispiel MyObject.SpecifySomeParameter(asdasd)funktioniert gut für mich.
Breandán Dalton

78

Nur meine 2 Cent;

Die Verkettung von Methoden macht das Debuggen schwierig: - Sie können den Haltepunkt nicht an einem präzisen Punkt platzieren, sodass Sie das Programm genau dort anhalten können, wo Sie es möchten. - Wenn eine dieser Methoden eine Ausnahme auslöst und Sie eine Zeilennummer erhalten, haben Sie keine Ahnung Welche Methode in der "Kette" hat das Problem verursacht?

Ich denke, es ist im Allgemeinen eine gute Praxis, immer sehr kurze und prägnante Zeilen zu schreiben. Jede Zeile sollte nur einen Methodenaufruf ausführen. Ziehen Sie mehr Zeilen längeren Zeilen vor.

BEARBEITEN: Kommentar erwähnt, dass Methodenverkettung und Zeilenumbruch getrennt sind. Das ist wahr. Abhängig vom Debugger kann es jedoch möglich sein, einen Haltepunkt in die Mitte einer Anweisung zu setzen oder nicht. Selbst wenn Sie dies können, bietet die Verwendung separater Zeilen mit Zwischenvariablen viel mehr Flexibilität und eine ganze Reihe von Werten, die Sie im Überwachungsfenster untersuchen können, um den Debugging-Prozess zu unterstützen.


4
Sind die Verwendung von Zeilenumbrüchen und Methodenverkettungen nicht unabhängig? Sie können mit jedem Aufruf in einer neuen Zeile gemäß @ Vilx-Antwort verketten und in der Regel mehrere separate Anweisungen in dieselbe Zeile einfügen (z. B. mithilfe von Semikolons in Java).
Brabster

2
Diese Antwort ist völlig gerechtfertigt, zeigt jedoch nur eine Schwäche in allen mir bekannten Debuggern und bezieht sich nicht speziell auf die Frage.
Masterxilo

1
@Brabster. Sie können für bestimmte Debugger getrennt sein. Wenn Sie jedoch separate Aufrufe mit Zwischenvariablen haben, erhalten Sie immer noch eine viel größere Fülle an Informationen, während Sie Fehler untersuchen.
RAY

4
+1, zu keinem Zeitpunkt wissen Sie, was jede Methode beim schrittweisen Debuggen einer Methodenkette zurückgegeben hat. Das Methodenkettenmuster ist ein Hack. Es ist der schlimmste Albtraum eines Wartungsprogrammierers.
Lee Kowalkowski

1
Ich bin auch kein großer Fan von Verkettung, aber warum nicht einfach einen Haltepunkt in die Methodendefinitionen einfügen?
Pankaj Sharma

39

Persönlich bevorzuge ich Verkettungsmethoden, die nur auf das ursprüngliche Objekt wirken, z. B. das Festlegen mehrerer Eigenschaften oder das Aufrufen von Methoden vom Typ Dienstprogramm.

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

Ich verwende es nicht, wenn eine oder mehrere der verketteten Methoden in meinem Beispiel ein anderes Objekt als foo zurückgeben würden. Während Sie syntaktisch alles verketten können, solange Sie die richtige API für dieses Objekt in der Kette verwenden, macht das Ändern von Objekten IMHO die Lesbarkeit weniger gut und kann sehr verwirrend sein, wenn die APIs für die verschiedenen Objekte Ähnlichkeiten aufweisen. Wenn Sie am Ende einig wirklich gemeinsamen Methodenaufruf tun ( .toString(), .print(), was auch immer) , der Gegenstand handeln Sie letztlich auf? Jemand, der den Code beiläufig liest, erkennt möglicherweise nicht, dass es sich um ein implizit zurückgegebenes Objekt in der Kette handelt und nicht um die ursprüngliche Referenz.

Das Verketten verschiedener Objekte kann auch zu unerwarteten Nullfehlern führen. In meinen Beispielen sind unter der Annahme, dass foo gültig ist, alle Methodenaufrufe "sicher" (z. B. gültig für foo). Im Beispiel des OP:

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

... gibt es keine Garantie (als externer Entwickler, der sich den Code ansieht), dass getSchedule tatsächlich ein gültiges Nicht-Null-Zeitplanobjekt zurückgibt. Das Debuggen dieses Codestils ist häufig sehr viel schwieriger, da viele IDEs den Methodenaufruf zum Zeitpunkt des Debuggens nicht als ein Objekt auswerten, das Sie überprüfen können. IMO, wann immer Sie ein Objekt benötigen, um es zu Debugging-Zwecken zu untersuchen, bevorzuge ich es in einer expliziten Variablen.


Wenn es eine Chance , dass Participantkeine gültige hat Scheduledann getScheduleMethode entwickelt , um eine Rückkehr Maybe(of Schedule)Art und saveToVerfahren entwickelt , um einen akzeptieren MaybeTypen.
Lightman

24

Martin Fowler hat hier eine gute Diskussion:

Methodenverkettung

Wann man es benutzt

Die Verkettung von Methoden kann die Lesbarkeit eines internen DSL erheblich verbessern und ist daher in manchen Köpfen fast zu einem Synonum für interne DSLs geworden. Die Methodenverkettung ist jedoch am besten, wenn sie in Verbindung mit anderen Funktionskombinationen verwendet wird.

Die Methodenverkettung ist besonders effektiv bei Grammatiken wie parent :: = (this | that) *. Die Verwendung verschiedener Methoden bietet eine lesbare Möglichkeit, um zu sehen, welches Argument als nächstes kommt. Ebenso können optionale Argumente mit Method Chaining leicht übersprungen werden. Eine Liste von obligatorischen Klauseln, wie z. B. parent :: = first second, funktioniert mit der Grundform nicht so gut, obwohl sie durch die Verwendung progressiver Schnittstellen gut unterstützt werden kann. Meistens würde ich Nested Function für diesen Fall bevorzugen.

Das größte Problem bei der Methodenverkettung ist das Endbearbeitungsproblem. Während es Problemumgehungen gibt, ist es normalerweise besser, eine verschachtelte Funktion zu verwenden, wenn Sie darauf stoßen. Verschachtelte Funktionen sind auch eine bessere Wahl, wenn Sie mit Kontextvariablen in Konflikt geraten.


2
Der Link ist tot :(
Qw3ry

Was meinst du mit DSL? Domain-spezifische Sprache
Sören

@ Sören: Fowler verweist auf domänenspezifische Sprachen.
Dirk Vollmar

21

Meiner Meinung nach ist die Verkettung von Methoden ein Novum. Sicher, es sieht cool aus, aber ich sehe keine wirklichen Vorteile darin.

Wie ist:

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

besser als:

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

Die Ausnahme kann sein, wenn addObject () ein neues Objekt zurückgibt. In diesem Fall ist der nicht verkettete Code möglicherweise etwas umständlicher wie:

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

9
Es ist prägnanter, da Sie bereits in Ihrem ersten Beispiel zwei der 'someList'-Teile vermeiden und am Ende eine Zeile anstelle von drei erhalten. Ob das nun gut oder schlecht ist, hängt von verschiedenen Dingen ab und ist vielleicht Geschmackssache.
Fabian Steeg

26
Der wahre Vorteil von 'someList' nur einmal ist, dass es dann viel einfacher ist, ihm einen längeren, aussagekräftigeren Namen zu geben. Jedes Mal, wenn ein Name mehrmals schnell hintereinander erscheinen muss, besteht die Tendenz, ihn kurz zu machen (um Wiederholungen zu reduzieren und die Lesbarkeit zu verbessern), was ihn weniger aussagekräftig macht und die Lesbarkeit beeinträchtigt.
Chris Dodd

Ein guter Punkt in Bezug auf Lesbarkeit und DRY-Prinzipien mit der Einschränkung, dass die Verkettung von Methoden die Selbstbeobachtung des Rückgabewerts einer bestimmten Methode verhindert und / oder die Annahme leerer Listen / Sätze und Nicht-Null-Werte für jede Methode in der Kette impliziert (ein Irrtum, der dazu führt viele NPEs in Systemen, die ich oft debuggen / reparieren muss).
Darrell Teague

@ChrisDodd Noch nie von der automatischen Vervollständigung gehört?
inf3rno

1
"Das menschliche Gehirn ist sehr gut darin, Textwiederholungen zu erkennen, wenn es dieselbe Einrückung aufweist" - genau das ist das Problem bei Wiederholungen: Es bewirkt, dass sich das Gehirn auf das sich wiederholende Muster konzentriert. Um den Code zu lesen UND ZU VERSTEHEN, müssen Sie Ihr Gehirn dazu zwingen, über das sich wiederholende Muster hinauszuschauen, um zu sehen, was wirklich vor sich geht. Dies liegt an den Unterschieden zwischen dem sich wiederholenden Muster, das Ihr Gehirn tendenziell beschönigt. Dies ist der Grund, warum Wiederholungen für die Lesbarkeit des Codes so schlecht sind.
Chris Dodd

7

Dies ist gefährlich, da Sie möglicherweise von mehr Objekten als erwartet abhängig sind. In diesem Fall gibt Ihr Aufruf eine Instanz einer anderen Klasse zurück:

Ich werde ein Beispiel geben:

foodStore ist ein Objekt, das aus vielen Lebensmittelgeschäften besteht, die Sie besitzen. foodstore.getLocalStore () gibt ein Objekt zurück, das Informationen zum Speicher enthält, der dem Parameter am nächsten liegt. getPriceforProduct (irgendetwas) ist eine Methode dieses Objekts.

Wenn Sie also foodStore.getLocalStore (Parameter) aufrufen .getPriceforProduct (irgendetwas)

Sie sind nicht nur auf FoodStore angewiesen, sondern auch auf LocalStore.

Sollte sich getPriceforProduct (irgendetwas) jemals ändern, müssen Sie nicht nur FoodStore ändern, sondern auch die Klasse, die die verkettete Methode aufgerufen hat.

Sie sollten immer eine lose Kopplung zwischen den Klassen anstreben.

Davon abgesehen verkette ich sie persönlich gerne, wenn ich Ruby programmiere.


7

Viele verwenden die Methodenverkettung als Annehmlichkeit, anstatt Bedenken hinsichtlich der Lesbarkeit zu berücksichtigen. Die Verkettung von Methoden ist akzeptabel, wenn dieselbe Aktion für dasselbe Objekt ausgeführt wird - jedoch nur, wenn die Lesbarkeit tatsächlich verbessert wird und nicht nur, um weniger Code zu schreiben.

Leider verwenden viele Methodenverkettungen gemäß den in der Frage angegebenen Beispielen. Während sie können noch lesbar gemacht werden, werden sie hohe Kopplung zwischen mehreren Klassen leider verursacht, so ist es nicht wünschenswert.


6

Das scheint irgendwie subjektiv zu sein.

Methodenverkettung ist nichts, was von Natur aus schlecht oder gut ist.

Lesbarkeit ist das Wichtigste.

(Bedenken Sie auch, dass eine große Anzahl verketteter Methoden die Dinge sehr zerbrechlich macht, wenn sich etwas ändert.)


Es könnte tatsächlich subjektiv sein, daher das subjektive Etikett. Ich hoffe, die Antworten werden mich hervorheben, in welchen Fällen Methodenverkettung eine gute Idee wäre - im Moment sehe ich nicht viel, aber ich denke, dies ist nur mein Versagen, die guten Punkte des Konzepts zu verstehen, anstatt etwas von Natur aus Schlechtes in der Verkettung selbst.
Ilari Kajaste

Wäre es nicht von Natur aus schlecht, wenn es zu einer hohen Kopplung führen würde? Das Aufteilen der Kette in einzelne Anweisungen verringert nicht die Lesbarkeit.
aberrant80

1
hängt davon ab, wie dogmatisch du sein willst. Wenn es zu etwas Lesbarerem führt, kann dies in vielen Situationen vorzuziehen sein. Das große Problem, das ich bei diesem Ansatz habe, ist, dass die meisten Methoden für ein Objekt Verweise auf das Objekt selbst zurückgeben, aber häufig gibt die Methode einen Verweis auf ein untergeordnetes Objekt zurück, auf dem Sie weitere Methoden verketten können. Sobald Sie damit beginnen, wird es für einen anderen Codierer sehr schwierig, die Vorgänge zu entwirren. Auch jede Änderung der Funktionalität einer Methode wird ein Problem beim Debuggen in einer großen zusammengesetzten Anweisung sein.
John Nicholas

6

Vorteile der Verkettung
dh wo ich sie gerne benutze

Ein Vorteil der Verkettung, den ich nicht erwähnt habe, war die Möglichkeit, ihn während der Variableninitiierung oder beim Übergeben eines neuen Objekts an eine Methode zu verwenden. Ich bin mir nicht sicher, ob dies eine schlechte Praxis ist oder nicht.

Ich weiß, dass dies ein erfundenes Beispiel ist, aber sagen Sie, dass Sie die folgenden Klassen haben

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

Angenommen, Sie haben keinen Zugriff auf die Basisklasse, oder Sie sagen, die Standardwerte sind dynamisch, basieren auf der Zeit usw. Ja, Sie könnten dann instanziieren und dann die Werte ändern, aber das kann umständlich werden, insbesondere wenn Sie nur übergeben die Werte zu einer Methode:

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

Aber ist das nicht viel einfacher zu lesen:

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

Oder was ist mit einem Klassenmitglied?

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

Auf diese Weise habe ich die Verkettung verwendet. Normalerweise dienen meine Methoden nur der Konfiguration. Sie sind also nur 2 Zeilen lang. Legen Sie dann einen Wert fest Return Me. Für uns hat es große Zeilen aufgeräumt, die sehr schwer zu lesen und zu verstehen sind. Code in einer Zeile, die wie ein Satz liest. etwas wie

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

Vs So etwas wie

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)

Nachteil der Verkettung,
dh wo ich es nicht gerne benutze

Ich verwende keine Verkettung, wenn viele Parameter an die Routinen übergeben werden müssen, hauptsächlich, weil die Zeilen sehr lang werden. Wie im OP erwähnt, kann es verwirrend werden, wenn Sie Routinen an andere Klassen aufrufen, um sie an eine der Routinen zu übergeben die Verkettungsmethoden.

Es besteht auch die Sorge, dass eine Routine ungültige Daten zurückgibt. Bisher habe ich die Verkettung nur verwendet, wenn ich dieselbe aufgerufene Instanz zurückgebe. Wie bereits erwähnt, erschweren Sie beim Debattieren zwischen Klassen das Debuggen (welches hat null zurückgegeben?) Und können die Abhängigkeitskopplung zwischen Klassen erhöhen.

Fazit

Wie alles im Leben und beim Programmieren ist das Verketten weder gut noch schlecht, wenn Sie das Schlechte vermeiden können, kann das Verketten ein großer Vorteil sein.

Ich versuche diese Regeln zu befolgen.

  1. Versuchen Sie, nicht zwischen Klassen zu verketten
  2. Machen Sie Routinen speziell für die Verkettung
  3. Mach nur EIN Ding in einer Verkettungsroutine
  4. Verwenden Sie es, wenn es die Lesbarkeit verbessert
  5. Verwenden Sie es, wenn es den Code einfacher macht

6

Durch die Verkettung von Methoden können erweiterte DSLs direkt in Java entworfen werden. Im Wesentlichen können Sie mindestens diese Arten von DSL-Regeln modellieren:

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

Diese Regeln können über diese Schnittstellen implementiert werden

// 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();
}

Mit diesen einfachen Regeln können Sie komplexe DSLs wie SQL direkt in Java implementieren, wie dies von jOOQ , einer von mir erstellten Bibliothek, durchgeführt wird. Ein ziemlich komplexes SQL-Beispiel aus meinem Blog finden Sie hier:

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()

Ein weiteres schönes Beispiel ist jRTF , ein kleines DSL, mit dem RTF-Dokumente direkt in Java bearbeitet werden können. Ein Beispiel:

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: Ja, es kann in so ziemlich jeder objektorientierten Programmiersprache verwendet werden, die etwas wie Schnittstellen und Subtyp-Polymorphismus kennt
Lukas Eder

4

Methodenverkettung mag in den meisten Fällen einfach eine Neuheit sein, aber ich denke, sie hat ihren Platz. Ein Beispiel könnte in CodeIgniters Active Record-Verwendung zu finden sein :

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

Das sieht viel sauberer aus (und macht meiner Meinung nach mehr Sinn) als:

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

Es ist wirklich subjektiv; Jeder hat seine eigene Meinung.


Dies ist ein Beispiel für eine fließende Schnittstelle mit Methodenverkettung, daher unterscheidet sich der UseCase hier geringfügig. Sie verketten nicht nur, sondern erstellen eine interne domänenspezifische Sprache, die sich leicht lesen lässt. Nebenbei bemerkt ist CIs ActiveRecord kein ActiveRecord.
Gordon

3

Ich denke, der primäre Irrtum besteht darin, dass dies im Allgemeinen ein objektorientierter Ansatz ist, obwohl es sich eher um einen funktionalen Programmieransatz als um irgendetwas anderes handelt.

Die Hauptgründe, warum ich es verwende, sind sowohl die Lesbarkeit als auch die Verhinderung, dass mein Code von Variablen überschwemmt wird.

Ich verstehe nicht wirklich, wovon andere sprechen, wenn sie sagen, dass dies die Lesbarkeit beeinträchtigt. Es ist eine der prägnantesten und kohärentesten Formen der Programmierung, die ich verwendet habe.

Auch das:

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

So würde ich es normalerweise benutzen. Ich benutze es normalerweise nicht, um x Anzahl von Parametern zu verketten. Wenn ich x Anzahl von Parametern in einen Methodenaufruf eingeben möchte, würde ich die Parameter- Syntax verwenden:

public void foo (params object [] items)

Und wandeln Sie die Objekte basierend auf dem Typ um oder verwenden Sie je nach Anwendungsfall einfach ein Datentyp-Array oder eine Sammlung.


1
+1 zu "Der primäre Irrtum besteht darin, dass dies im Allgemeinen ein objektorientierter Ansatz ist, obwohl es sich eher um einen funktionalen Programmieransatz als um irgendetwas anderes handelt." Der herausragende Anwendungsfall ist für zustandslose Manipulationen an Objekten (bei denen Sie anstelle einer Änderung des Status ein neues Objekt zurückgeben, auf das Sie weiterhin einwirken). Die Fragen und anderen Antworten des OP zeigen zustandsbehaftete Handlungen, die bei der Verkettung tatsächlich unangenehm erscheinen.
OmerB

Ja, Sie haben Recht, es handelt sich um eine Manipulation ohne Status, außer dass ich normalerweise kein neues Objekt erstelle, sondern stattdessen die Abhängigkeitsinjektion verwende, um es zu einem verfügbaren Dienst zu machen. Und ja, Stateful Use Cases sind meiner Meinung nach nicht das, wofür die Methodenverkettung gedacht war. Die einzige Ausnahme, die ich sehe, ist, wenn Sie den DI-Dienst mit einigen Einstellungen initialisieren und eine Art Watchdog haben, um den Status wie einen COM-Dienst zu überwachen. Nur IMHO.
Shane Thorndike

2

Ich stimme zu, ich habe daher die Art und Weise geändert, wie eine fließende Schnittstelle in meiner Bibliothek implementiert wurde.

Vor:

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

Nach dem:

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

In der "Vorher" -Implementierung haben die Funktionen das Objekt geändert und endeten in return this. Ich habe die Implementierung geändert, um ein neues Objekt des gleichen Typs zurückzugeben .

Meine Begründung für diese Änderung :

  1. Der Rückgabewert hatte nichts mit der Funktion zu tun, er war nur dazu da, den Verkettungsteil zu unterstützen. Es hätte laut OOP eine void-Funktion sein sollen.

  2. Die Methodenverkettung in Systembibliotheken implementiert sie auch so (wie linq oder string):

    myText = myText.trim().toUpperCase();
    
  3. Das ursprüngliche Objekt bleibt erhalten, sodass der API-Benutzer entscheiden kann, was damit geschehen soll. Es ermöglicht:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Eine Kopierimplementierung kann auch zum Erstellen von Objekten verwendet werden:

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

    Wo die setBackground(color)Funktion die Instanz ändert und nichts zurückgibt (wie es soll) .

  5. Das Verhalten der Funktionen ist vorhersehbarer (siehe Punkt 1 und 2).

  6. Die Verwendung eines kurzen Variablennamens kann auch die Code-Unordnung verringern, ohne dem Modell eine API aufzuzwingen.

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

Fazit:
Meiner Meinung nach ist eine fließende Schnittstelle, die eine return thisImplementierung verwendet, einfach falsch.


1
Aber würde die Rückgabe einer neuen Instanz für jeden Aufruf nicht einen erheblichen Overhead verursachen, insbesondere wenn Sie größere Elemente verwenden? Zumindest für nicht verwaltete Sprachen.
Apeiron

@Apeiron In Bezug auf die Leistung ist es definitiv schneller zu return thisverwalten oder auf andere Weise. Ich bin der Meinung, dass es auf "nicht natürliche" Weise eine fließende API erhält. (Grund 6 hinzugefügt: um eine nicht fließende Alternative zu zeigen, die nicht über den Overhead / die zusätzliche Funktionalität verfügt)
Bob Fanger

Ich stimme mit Ihnen ein. Es ist meistens besser, den zugrunde liegenden Status eines DSL unberührt zu lassen und stattdessen bei jedem Methodenaufruf neue Objekte zurückzugeben ... Ich bin neugierig: Was ist die Bibliothek, die Sie erwähnt haben?
Lukas Eder

1

Der völlig übersehene Punkt hier ist, dass die Methodenverkettung DRY ermöglicht . Es ist ein effektiver Ersatz für das "mit" (das in einigen Sprachen schlecht implementiert ist).

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

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

Dies ist aus dem gleichen Grund wichtig, aus dem DRY immer wichtig ist. Wenn sich herausstellt, dass A ein Fehler ist und diese Vorgänge für B ausgeführt werden müssen, müssen Sie nur an einer Stelle aktualisieren, nicht an 3.

Pragmatisch gesehen ist der Vorteil in diesem Fall gering. Trotzdem, ein bisschen weniger tippen, ein bisschen robuster (trocken), nehme ich es.


14
Das Wiederholen eines Variablennamens im Quellcode hat nichts mit dem DRY-Prinzip zu tun. DRY stellt fest, dass "jedes Wissen eine einzige, eindeutige, maßgebliche Darstellung innerhalb eines Systems haben muss" oder mit anderen Worten die Wiederholung von Wissen (nicht die Wiederholung von Text ) vermeiden sollte .
Pedromanoel

4
Es ist mit Sicherheit eine Wiederholung, die gegen die Trockenheit verstößt. Das Wiederholen von Variablennamen (unnötig) verursacht alles Böse auf die gleiche Weise wie andere Formen des Trockens: Es schafft mehr Abhängigkeiten und mehr Arbeit. Wenn wir im obigen Beispiel A umbenennen, benötigt die Wet- Version 3 Änderungen und führt zum Debuggen von Fehlern, wenn einer der drei Fehler übersehen wird.
Anfurny

1
Ich kann das Problem, auf das Sie in diesem Beispiel hingewiesen haben, nicht sehen, da alle Methodenaufrufe nahe beieinander liegen. Wenn Sie vergessen, den Namen einer Variablen in einer Zeile zu ändern, gibt der Compiler einen Fehler zurück, der korrigiert wird, bevor Sie Ihr Programm ausführen können. Außerdem ist der Name einer Variablen auf den Umfang ihrer Deklaration beschränkt. Es sei denn, diese Variable ist global, was bereits eine schlechte Programmierpraxis ist. IMO, DRY geht es nicht darum, weniger zu tippen, sondern die Dinge isoliert zu halten.
Pedromanoel

Der "Compiler" gibt möglicherweise einen Fehler zurück, oder Sie verwenden PHP oder JS, und der Interpreter gibt möglicherweise nur zur Laufzeit einen Fehler aus, wenn Sie diese Bedingung erfüllen.
Anfurny

1

Ich hasse es im Allgemeinen, Methoden zu verketten, weil ich denke, dass dies die Lesbarkeit verschlechtert. Kompaktheit wird oft mit Lesbarkeit verwechselt, aber es handelt sich nicht um dieselben Begriffe. Wenn Sie alles in einer einzigen Anweisung ausführen, ist dies kompakt, aber meistens weniger lesbar (schwerer zu befolgen) als in mehreren Anweisungen. Wie Sie bemerkt haben, kann die Verkettung von Methoden Verwirrung stiften, es sei denn, Sie können nicht garantieren, dass der Rückgabewert der verwendeten Methoden gleich ist.

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'));

Wie Sie sehen, gewinnen Sie so gut wie nichts, weil Sie Ihrer einzelnen Anweisung Zeilenumbrüche hinzufügen müssen, um sie besser lesbar zu machen, und Einrückungen hinzufügen müssen, um zu verdeutlichen, dass Sie über verschiedene Objekte sprechen. Wenn ich eine identitätsbasierte Sprache verwenden möchte, würde ich stattdessen Python lernen, ganz zu schweigen davon, dass die meisten IDEs die Einrückung durch automatische Formatierung des Codes entfernen.

Ich denke, der einzige Ort, an dem diese Art der Verkettung nützlich sein kann, ist das Weiterleiten von Streams in CLI oder das Zusammenfügen mehrerer Abfragen in SQL. Beide haben einen Preis für mehrere Aussagen. Wenn Sie jedoch komplexe Probleme lösen möchten, müssen Sie selbst diejenigen bezahlen, die den Preis zahlen und den Code in mehreren Anweisungen mithilfe von Variablen schreiben oder Bash-Skripte und gespeicherte Prozeduren oder Ansichten schreiben.

Zu den DRY-Interpretationen: "Vermeiden Sie die Wiederholung von Wissen (nicht die Wiederholung von Text)." und "Tippe weniger, wiederhole nicht einmal Texte.", der erste, was das Prinzip wirklich bedeutet, aber der zweite ist ein weit verbreitetes Missverständnis, weil viele Menschen überkomplizierten Bullshit nicht verstehen können wie "Jedes Wissen muss einen einzigen, eindeutigen haben, maßgebliche Darstellung innerhalb eines Systems ". Die zweite ist die Kompaktheit um jeden Preis, die in diesem Szenario bricht, weil sie die Lesbarkeit verschlechtert. Die erste Interpretation wird durch DDD unterbrochen, wenn Sie Code zwischen begrenzten Kontexten kopieren, da in diesem Szenario eine lose Kopplung wichtiger ist.


0

Die gute:

  1. Es ist knapp, ermöglicht es Ihnen jedoch, mehr elegant in eine einzelne Zeile zu setzen.
  2. Sie können manchmal die Verwendung einer Variablen vermeiden, was gelegentlich nützlich sein kann.
  3. Es kann besser funktionieren.

Das Schlechte:

  1. Sie implementieren Rückgaben und fügen Methoden zu Objekten im Wesentlichen Funktionen hinzu, die nicht wirklich Teil dessen sind, was diese Methoden tun sollen. Es gibt etwas zurück, das Sie bereits haben, um nur ein paar Bytes zu speichern.
  2. Es verbirgt Kontextwechsel, wenn eine Kette zu einer anderen führt. Sie können dies mit Getter erhalten, außer es ist ziemlich klar, wenn der Kontext wechselt.
  3. Das Verketten über mehrere Zeilen sieht hässlich aus, spielt nicht gut mit Einrückungen und kann zu Verwirrung beim Umgang mit Bedienern führen (insbesondere in Sprachen mit ASI).
  4. Wenn Sie etwas anderes zurückgeben möchten, das für eine verkettete Methode nützlich ist, wird es Ihnen möglicherweise schwerer fallen, das Problem zu beheben oder mehr Probleme damit zu haben.
  5. Sie verlagern die Kontrolle an eine Entität, an die Sie normalerweise nicht nur aus praktischen Gründen auslagern würden, selbst in streng typisierten Sprachen. Fehler, die dadurch verursacht werden, können nicht immer erkannt werden.
  6. Es kann schlechter abschneiden.

Allgemeines:

Ein guter Ansatz besteht darin, die Verkettung im Allgemeinen erst dann zu verwenden, wenn Situationen auftreten oder bestimmte Module dafür besonders geeignet sind.

Verkettung kann in einigen Fällen die Lesbarkeit erheblich beeinträchtigen, insbesondere beim Wiegen in Punkt 1 und 2.

Bei der Akasation kann es missbraucht werden, z. B. anstelle eines anderen Ansatzes (z. B. Übergeben eines Arrays) oder durch Mischen von Methoden auf bizarre Weise (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).


0

Stellungnahme

Der größte Nachteil der Verkettung besteht darin, dass es für den Leser schwierig sein kann zu verstehen, wie sich jede Methode auf das ursprüngliche Objekt auswirkt, wenn dies der Fall ist, und welchen Typ jede Methode zurückgibt.

Einige Fragen:

  • Geben Methoden in der Kette ein neues Objekt zurück oder dasselbe Objekt mutiert?
  • Geben alle Methoden in der Kette den gleichen Typ zurück?
  • Wenn nicht, wie wird angezeigt, wenn sich ein Typ in der Kette ändert?
  • Kann der von der letzten Methode zurückgegebene Wert sicher verworfen werden?

In den meisten Sprachen kann das Debuggen beim Verketten tatsächlich schwieriger sein. Selbst wenn sich jeder Schritt in der Kette in einer eigenen Linie befindet (was den Zweck der Verkettung zunichte macht), kann es schwierig sein, den nach jedem Schritt zurückgegebenen Wert zu überprüfen, insbesondere bei nicht mutierenden Methoden.

Die Kompilierungszeiten können je nach Sprache und Compiler langsamer sein, da die Auflösung von Ausdrücken sehr viel komplexer sein kann.

Ich glaube, dass Verketten wie bei allem eine gute Lösung ist, die in manchen Szenarien nützlich sein kann. Es sollte mit Vorsicht angewendet werden, um die Auswirkungen zu verstehen und die Anzahl der Kettenelemente auf wenige zu beschränken.


0

In typisierten Sprachen (die fehlen autooder gleichwertig sind) muss der Implementierer die Typen der Zwischenergebnisse nicht deklarieren.

import Participant
import Schedule

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

Bei längeren Ketten, bei denen es sich möglicherweise um mehrere Zwischentypen handelt, müssen Sie jeden von ihnen deklarieren.

Ich glaube, dass dieser Ansatz wirklich in Java entwickelt wurde, wo a) alle Funktionsaufrufe Aufrufe von Mitgliedsfunktionen sind und b) explizite Typen erforderlich sind. Natürlich gibt es hier einen Kompromiss, der die Aussagekraft verliert, aber in manchen Situationen finden es einige Leute lohnenswert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.