Ich verstehe das Motiv hinter dem Prinzip des geringsten Wissens , finde aber einige Nachteile, wenn ich versuche, es in meinem Design anzuwenden.
Eines der Beispiele für dieses Prinzip (eigentlich, wie man es nicht verwendet), das ich im Buch Head First Design Patterns gefunden habe , besagt , dass es im Sinne dieses Prinzips falsch ist, eine Methode für Objekte aufzurufen, die vom Aufrufen anderer Methoden zurückgegeben wurden .
Aber es scheint, dass es manchmal sehr notwendig ist, solche Fähigkeiten zu nutzen.
Zum Beispiel: Ich habe mehrere Klassen: Video-Capture-Klasse, Encoder-Klasse, Streamer-Klasse und alle verwenden eine andere grundlegende Klasse, VideoFrame, und da sie miteinander interagieren, können sie zum Beispiel Folgendes tun:
streamer
Klassencode
...
frame = encoder->WaitEncoderFrame()
frame->DoOrGetSomething();
....
Wie Sie sehen, wird dieses Prinzip hier nicht angewendet. Kann dieses Prinzip hier angewendet werden, oder ist es so, dass dieses Prinzip bei einem Entwurf wie diesem nicht immer angewendet werden kann?
quelle
Antworten:
Das Prinzip, von dem Sie sprechen (besser bekannt als Law of Demeter ) , kann angewendet werden, indem Sie Ihrer Streamer-Klasse eine weitere Hilfsmethode wie hinzufügen
Jetzt "spricht" jede Funktion nur mit Freunden, nicht mit "Freunden von Freunden".
IMHO ist es eine grobe Richtlinie, die helfen kann, Methoden zu entwickeln, die strenger dem Prinzip der Einzelverantwortung folgen. In einem einfachen Fall wie dem obigen ist es wahrscheinlich sehr fraglich, ob dies den Aufwand wirklich wert ist und ob der resultierende Code wirklich "sauberer" ist oder ob er Ihren Code nur formal ohne nennenswerten Gewinn erweitert.
quelle
Das Prinzip des geringsten Wissens oder das Gesetz von Demeter ist eine Warnung davor, Ihre Klasse mit Details anderer Klassen zu verwickeln, die Schicht für Schicht durchlaufen. Es sagt Ihnen, dass es besser ist, nur mit Ihren "Freunden" und nicht mit "Freunden von Freunden" zu sprechen.
Stellen Sie sich vor, Sie wurden gebeten, einen Schild an eine Statue eines Ritters in glänzender Plattenrüstung zu schweißen. Sie positionieren den Schild vorsichtig auf dem linken Arm, damit er natürlich aussieht. Sie bemerken, dass es drei kleine Stellen am Unterarm, am Ellbogen und am Oberarm gibt, an denen der Schild zufällig die Rüstung berührt. Sie schweißen alle drei Stellen, weil Sie sicherstellen möchten, dass die Verbindung fest ist. Stellen Sie sich vor, Ihr Chef ist verrückt, weil er den Ellbogen seiner Rüstung nicht bewegen kann. Sie nahmen an, dass sich die Rüstung niemals bewegen würde, und stellten so eine unbewegliche Verbindung zwischen Unterarm und Oberarm her. Der Schild sollte sich nur mit seinem Freund, dem Unterarm, verbinden. Nicht zu Unterarmfreunden. Auch wenn Sie ein Stück Metall hinzufügen müssen, damit sie sich berühren.
Metaphern sind nett, aber was meinen wir eigentlich mit Freund? Alles, was ein Objekt zu erstellen oder zu finden weiß, ist ein Freund. Alternativ kann ein Objekt nur verlangen, dass ihm andere Objekte übergeben werden , von denen es nur die Schnittstelle kennt. Diese zählen nicht als Freunde, da keine Erwartung auferlegt wird, wie man sie bekommt. Wenn das Objekt nicht weiß, woher es kommt, weil etwas anderes es passiert / injiziert hat, dann ist es kein Freund eines Freundes, es ist nicht einmal ein Freund. Es ist etwas, das das Objekt nur zu verwenden weiß. Das ist gut.
Wenn Sie versuchen, Prinzipien wie diese anzuwenden, ist es wichtig zu verstehen, dass sie Sie niemals daran hindern, etwas zu erreichen. Dies ist eine Warnung, dass Sie möglicherweise nicht mehr daran arbeiten, um ein besseres Design zu erzielen, das dasselbe erreicht.
Niemand möchte ohne Grund arbeiten, daher ist es wichtig zu verstehen, was Sie daraus machen. In diesem Fall bleibt Ihr Code flexibel. Sie können Änderungen vornehmen und weniger andere Klassen von den Änderungen beeinflussen lassen, um die Sie sich Sorgen machen müssen. Das hört sich gut an, hilft Ihnen aber nicht bei der Entscheidung, was Sie tun sollen, es sei denn, Sie betrachten es als eine Art religiöse Lehre.
Anstatt dieses Prinzip blind zu befolgen, nehmen Sie eine einfache Version dieses Problems. Schreiben Sie eine Lösung, die nicht dem Prinzip folgt, und eine, die es tut. Jetzt, da Sie zwei Lösungen haben, können Sie vergleichen, wie empfänglich jede für Änderungen ist, indem Sie versuchen, sie in beiden umzusetzen.
Wenn Sie ein Problem nicht lösen können, während Sie diesem Prinzip folgen, fehlt Ihnen wahrscheinlich eine weitere Fähigkeit.
Eine Lösung für Ihr spezielles Problem besteht darin, das
frame
in etwas (eine Klasse oder Methode) einzufügen, das weiß, wie mit Frames gesprochen wird, damit Sie nicht all diese Frame-Chat-Details in Ihrer Klasse verbreiten müssen, die jetzt nur wissen, wie und wann einen Rahmen bekommen.Das folgt eigentlich einem anderen Prinzip: Getrennte Nutzung von Konstruktion.
Indem Sie diesen Code verwenden, haben Sie die Verantwortung dafür übernommen, auf irgendeine Weise eine zu erwerben
Frame
. Sie haben noch keine Verantwortung für das Gespräch mit a übernommenFrame
.Jetzt musst du wissen, wie man mit a spricht
Frame
, aber ersetze das durch:Und jetzt müssen Sie nur noch wissen, wie Sie mit Ihrem Freund FrameHandler sprechen können.
Es gibt viele Möglichkeiten, dies zu erreichen, und dies ist vielleicht nicht die beste, aber es zeigt, wie die Befolgung des Prinzips das Problem nicht unlösbar macht. Es fordert nur mehr Arbeit von dir.
Jede gute Regel hat eine Ausnahme. Die besten Beispiele, die ich kenne, sind interne domänenspezifische Sprachen . Eine DSL s Methodenkettescheint das Gesetz von Demeter ständig zu missbrauchen, weil Sie ständig Methoden aufrufen, die verschiedene Typen zurückgeben, und diese direkt verwenden. Warum ist das in Ordnung? Denn bei einem DSL ist alles, was zurückgegeben wird, ein sorgfältig entworfener Freund, mit dem Sie direkt sprechen sollen. Sie haben das Recht, von der Methodenkette eines DSL zu erwarten, dass sie sich nicht ändert. Sie haben dieses Recht nicht, wenn Sie sich nur zufällig mit der Verkettung der Codebasis befassen, was auch immer Sie finden. Die besten DSLs sind sehr dünne Darstellungen oder Schnittstellen zu anderen Objekten, auf die Sie wahrscheinlich auch nicht eingehen sollten. Ich erwähne dies nur, weil ich das Gesetz der Demeter viel besser verstanden habe, als ich erfuhr, warum DSLs ein gutes Design sind. Einige gehen sogar so weit zu behaupten, dass DSLs nicht einmal gegen das wahre Gesetz von Demeter verstoßen.
Eine andere Lösung besteht darin, etwas anderes
frame
in dich spritzen zu lassen. Wenn Sieframe
von einem Setter oder vorzugsweise einem Konstrukteur kommen, übernehmen Sie keine Verantwortung für die Konstruktion oder den Erwerb eines Rahmens. Das bedeutet, dass deine Rolle hier viel mehr so ist, wieFrameHandlers
sie sein würde. Stattdessen sind Sie jetzt derjenige, der mitFrame
etwas anderem plaudert und herausfindet, wie man eineFrame
In gewisser Weise gleiche Lösung mit einem einfachen Perspektivenwechsel erhält .Die SOLID- Prinzipien sind die großen, denen ich zu folgen versuche. Zwei davon sind die Prinzipien der Einzelverantwortung und der Abhängigkeitsumkehr. Es ist wirklich schwer, diese beiden zu respektieren und trotzdem das Gesetz von Demeter zu verletzen.
Die Mentalität, mit der Demeter verletzt wird, ist so, als würde man in einem Buffetrestaurant essen, in dem man sich einfach schnappt, was man will. Mit ein wenig Vorarbeit können Sie sich selbst ein Menü und einen Server zur Verfügung stellen, der Ihnen alles bringt, was Sie möchten. Lehnen Sie sich zurück, entspannen Sie sich und geben Sie ein gutes Trinkgeld.
quelle
Ist funktionales Design besser als objektorientiertes Design? Es hängt davon ab, ob.
Ist MVVM besser als MVC? Es hängt davon ab, ob.
Amos & Andy oder Martin und Lewis? Es hängt davon ab, ob.
Wovon hängt es ab? Die Auswahl, die Sie treffen, hängt davon ab, wie gut jede Technik oder Technologie die funktionalen und nicht funktionalen Anforderungen Ihrer Software erfüllt und gleichzeitig Ihre Design-, Leistungs- und Wartbarkeitsziele angemessen erfüllt.
Wenn Sie dies in einem Buch oder Blog lesen, bewerten Sie die Behauptung anhand ihres Verdienstes. das heißt, fragen Sie warum. Es gibt keine richtige oder falsche Technik in der Softwareentwicklung, es gibt nur "wie gut erfüllt diese Technik meine Ziele? Ist sie effektiv oder unwirksam? Löst sie ein Problem, sondern schafft sie ein neues? Kann sie von allen gut verstanden werden?" Entwicklungsteam, oder ist es zu dunkel? "
In diesem speziellen Fall - dem Aufrufen einer Methode für ein Objekt, das von einer anderen Methode zurückgegeben wird - ist es schwer vorstellbar, wie man die Behauptung aufstellen kann, dass es sich um eine kategorische Methode handelt, da es ein tatsächliches Entwurfsmuster gibt, das diese Praxis (Factory) kodiert falsch.
Der Grund, warum es das "Prinzip des geringsten Wissens" genannt wird, ist, dass "geringe Kopplung" eine wünschenswerte Qualität eines Systems ist. Objekte, die nicht eng miteinander verbunden sind, arbeiten unabhängiger und können daher einfacher einzeln verwaltet und geändert werden. Aber wie Ihr Beispiel zeigt, ist manchmal eine hohe Kopplung wünschenswerter, damit Objekte ihre Bemühungen effektiver koordinieren können.
quelle
Die Antwort von Doc Brown zeigt eine klassische Lehrbuchimplementierung von Law of Demeter - und der Verdruss, auf diese Weise Dutzende von Methoden hinzuzufügen, ist wahrscheinlich der Grund, warum sich Programmierer, einschließlich mir, oft nicht darum kümmern, selbst wenn sie es sollten.
Es gibt eine alternative Möglichkeit, die Hierarchie von Objekten zu entkoppeln:
Im Fall von Original Poster (OPs)
encoder->WaitEncoderFrame()
würdeIEncoderFrame
anstelle von a ein zurückgegebenFrame
und definiert, welche Operationen zulässig sind.LÖSUNG 1
Im einfachsten Fall
Frame
und wennEncoder
beide Klassen von Ihnen gesteuert werden,IEncoderFrame
handelt es sich um eine Teilmenge von Methoden, die Frame bereits öffentlich verfügbar macht, und es ist derEncoder
Klasse egal, was Sie mit diesem Objekt tun. Die Implementierung ist dann trivial ( Code in c # ):LÖSUNG 2
In einem Zwischenfall, in dem die
Frame
Definition nicht unter Ihrer Kontrolle steht oder dieIEncoderFrame
Methoden nicht hinzugefügt werden sollten ,Frame
ist ein Adapter eine gute Lösung . Das ist die Antwort von CandiedOrange , asnew FrameHandler( frame )
. WICHTIG: Wenn Sie dies tun, ist es flexibler, wenn Sie es als Schnittstelle und nicht als Klasse verfügbar machen .Encoder
muss wissenclass FrameHandler
, aber Kunden müssen nur wisseninterface IFrameHandler
. Oder wie ich es nannte,interface IEncoderFrame
- um anzuzeigen, dass es sich speziell um ein Frame handelt, wie es vom POV des Encoders aus gesehen wird :COST: Zuweisung und GC eines neuen Objekts, des EncoderFrameWrapper, bei jedem
encoder.TheFrame
Aufruf. (Sie könnten diesen Wrapper zwischenspeichern, aber das fügt mehr Code hinzu. Und es ist nur einfach, zuverlässig zu codieren, wenn das Frame-Feld des Encoders nicht durch einen neuen Frame ersetzt werden kann.)LÖSUNG 3
In dem schwierigeren Fall, müßte die neue Wrapper über beide weiß
Encoder
undFrame
. Dieses Objekt würde selbst gegen LoD verstoßen - es manipuliert eine Beziehung zwischen Encoder und Frame, die in der Verantwortung von Encoder liegen sollte - und es wäre wahrscheinlich ein Schmerz, das Richtige zu tun. Folgendes kann passieren, wenn Sie diesen Weg beschreiten:Das wurde hässlich. Es gibt eine weniger komplizierte Implementierung, wenn der Wrapper Details seines Erstellers / Besitzers (Encoders) berühren muss:
Zugegeben, wenn ich wüsste, dass ich hier landen würde, würde ich das vielleicht nicht tun. Könnte nur die LoD-Methoden schreiben, und damit fertig sein. Es muss keine Schnittstelle definiert werden. Andererseits gefällt mir, dass die Schnittstelle verwandte Methoden zusammenfasst. Mir gefällt, wie es sich anfühlt, die "rahmenähnlichen Operationen" anzufertigen, die sich wie ein Rahmen anfühlen.
LETZTE KOMMENTARE
Bedenken Sie Folgendes: Wenn der Implementierer von der
Encoder
Ansicht war, dass das Exponieren derFrame frame
Gesamtarchitektur angemessen oder "so viel einfacher als das Implementieren von LoD" ist, wäre es viel sicherer gewesen, wenn er stattdessen das erste von mir gezeigte Snippet ausgeführt hätte - Exponieren einer begrenzten Teilmenge von Frame als Schnittstelle. Nach meiner Erfahrung ist das oft eine völlig praktikable Lösung. Fügen Sie der Schnittstelle nach Bedarf Methoden hinzu. (Ich spreche von einem Szenario, in dem wir wissen, dass Frame bereits über die erforderlichen Methoden verfügt, oder das Hinzufügen dieser Methoden ist einfach und unumstritten. Die "Implementierungsarbeit" für jede Methode besteht darin, der Schnittstellendefinition eine Zeile hinzuzufügen.) Und wissen, dass es auch im schlimmsten zukünftigen Szenario möglich ist, diese API funktionsfähig zu halten - hier,IEncoderFrame
Frame
Encoder
.Beachten Sie auch , dass , wenn Sie nicht über die Berechtigung hinzufügen müssen ,
IEncoderFrame
zuFrame
, oder die benötigten Methoden passen nicht gut zu der allgemeinenFrame
Klasse und Lösung # 2 nicht zu Ihnen passen, vielleicht wegen der zusätzlichen Objekterstellung-and-Zerstörung, Lösung Nr. 3 kann einfach als ein Weg gesehen werden, die Methoden zu organisierenEncoder
, um das LoD zu erreichen. Gehen Sie nicht nur Dutzende von Methoden durch. Binden Sie sie in eine Schnittstelle ein und verwenden Sie "Explizite Schnittstellenimplementierung" (wenn Sie sich in c # befinden), sodass auf sie nur zugegriffen werden kann, wenn das Objekt über diese Schnittstelle angezeigt wird.Ein weiterer Punkt, den ich hervorheben möchte, ist, dass die Entscheidung , Funktionalität als Schnittstelle verfügbar zu machen, alle drei oben beschriebenen Situationen bewältigt hat. Im ersten Fall
IEncoderFrame
handelt es sich einfach um eine Teilmenge derFrame
Funktionalität. In der zweitenIEncoderFrame
ist ein Adapter. In der drittenIEncoderFrame
ist eine Partition inEncoder
s Funktionalität. Es spielt keine Rolle, ob sich Ihre Anforderungen zwischen diesen drei Situationen ändern: Die API bleibt gleich.quelle