Es gibt das klassische OOP-Problem der Methodenverkettung im Vergleich zu "Single-Access-Point" -Methoden:
main.getA().getB().getC().transmogrify(x, y)
vs
main.getA().transmogrifyMyC(x, y)
Die erste scheint den Vorteil zu haben, dass jede Klasse nur für eine kleinere Menge von Operationen verantwortlich ist und alles viel modularer macht - das Hinzufügen einer Methode zu C erfordert keinen Aufwand in A, B oder C, um sie verfügbar zu machen.
Der Nachteil ist natürlich die schwächere Kapselung , die der zweite Code löst. Jetzt hat A die Kontrolle über jede Methode, die es durchläuft, und kann es auf Wunsch an seine Felder delegieren.
Mir ist klar, dass es keine einheitliche Lösung gibt und das hängt natürlich vom Kontext ab, aber ich würde gerne etwas über andere wichtige Unterschiede zwischen den beiden Stilen erfahren und unter welchen Umständen sollte ich einen von beiden bevorzugen - denn gerade jetzt, wenn ich es versuche Zum Entwerfen von Code habe ich das Gefühl, dass ich die Argumente einfach nicht für die eine oder andere Entscheidung benutze.
Im Allgemeinen versuche ich, die Verkettung der Methoden so gering wie möglich zu halten (basierend auf dem Gesetz von Demeter ).
Die einzige Ausnahme, die ich mache, ist für fließende Schnittstellen / interne DSL-Programmierung.
Martin Fowler unterscheidet in domänenspezifischen Sprachen ähnlich, jedoch aus Gründen der Trennung von Befehlsabfragen, in denen Folgendes angegeben ist:
Fowler sagt in seinem Buch auf Seite 70:
quelle
Ich denke die Frage ist, ob Sie eine passende Abstraktion verwenden.
Im ersten Fall haben wir
Wobei main vom Typ ist
IHasGetA
. Die Frage ist: Ist diese Abstraktion geeignet? Die Antwort ist nicht trivial. Und in diesem Fall sieht es ein bisschen anders aus, aber es ist trotzdem ein theoretisches Beispiel. Aber um ein anderes Beispiel zu konstruieren:Ist oft besser als
Da im letzteren Beispiel beide
this
und istmain
abhängig von den Typenv
,w
,x
,y
undz
. Außerdem sieht der Code nicht viel besser aus, wenn jede Methodendeklaration ein halbes Dutzend Argumente enthält.Ein Service Locator benötigt tatsächlich den ersten Ansatz. Sie möchten nicht auf die Instanz zugreifen, die über den Service Locator erstellt wird.
Das "Durchgreifen" eines Objekts kann also zu einer großen Abhängigkeit führen, zumal es auf Eigenschaften tatsächlicher Klassen basiert.
Eine Abstraktion zu erschaffen, bei der es darum geht, ein Objekt bereitzustellen, ist jedoch eine ganz andere Sache.
Zum Beispiel könnten Sie haben:
Wo
main
ist eine Instanz vonMain
. Wenn die Klassenkenntnismain
die Abhängigkeit aufIHasGetA
anstatt auf reduziertMain
, wird die Kopplung tatsächlich als recht gering empfunden. Der aufrufende Code weiß nicht einmal, dass er tatsächlich die letzte Methode für das ursprüngliche Objekt aufruft, was den Grad der Entkopplung tatsächlich veranschaulicht.Sie gehen eher einen Weg von präzisen und orthogonalen Abstraktionen, als tief in die Interna einer Implementierung hinein.
quelle
Das Gesetz von Demeter schlägt, wie @ Péter Török betont, die "kompakte" Form vor.
Je mehr Methoden Sie explizit in Ihrem Code erwähnen, desto mehr Klassen sind für Ihre Klasse von Bedeutung, wodurch die Wartungsprobleme zunehmen. In Ihrem Beispiel hängt die kompakte Form von zwei Klassen ab, während die längere Form von vier Klassen abhängt. Die längere Form widerspricht nicht nur dem Gesetz von Demeter; Außerdem können Sie Ihren Code jedes Mal ändern, wenn Sie eine der vier referenzierten Methoden ändern (im Gegensatz zu zwei in der kompakten Form).
quelle
A
explodieren wird,A
wenn viele Methoden sowieso abtreten möchten. Trotzdem stimme ich den Abhängigkeiten zu - das reduziert die Anzahl der vom Client-Code benötigten Abhängigkeiten drastisch.Ich habe mit diesem Problem selbst gerungen. Der Nachteil, wenn Sie tief in verschiedene Objekte "hineingreifen", ist, dass Sie beim Refactoring eine Menge Code ändern müssen, da es so viele Abhängigkeiten gibt. Außerdem wird Ihr Code etwas aufgebläht und schwerer zu lesen.
Andererseits bedeutet Klassen, die Methoden einfach "weitergeben", auch den Aufwand, mehrere Methoden an mehr als einer Stelle deklarieren zu müssen.
Eine Lösung, die dies abschwächt und in einigen Fällen angemessen ist, besteht darin, eine Factory-Klasse zu haben, die ein Fassadenobjekt erstellt, indem Daten / Objekte aus den entsprechenden Klassen kopiert werden. Auf diese Weise können Sie gegen Ihr Fassadenobjekt codieren und bei der Umgestaltung einfach die Logik der Fabrik ändern.
quelle
Ich stelle oft fest, dass die Logik eines Programms mit verketteten Methoden einfacher zu verstehen ist. Für mich
customer.getLastInvoice().itemCount()
passt das besser in mein Gehirn alscustomer.countLastInvoiceItems()
.Ob sich der Wartungsaufwand für die zusätzliche Kupplung lohnt, liegt bei Ihnen. (Ich mag auch kleine Funktionen in kleinen Klassen, deshalb neige ich dazu zu verketten. Ich sage nicht, dass es richtig ist - es ist genau das, was ich tue.)
quelle