Die Law of Demeter verhindert nicht die Übergabe von Objekten in Klassenkonstruktoren. Es verbietet jedoch, das gleiche Objekt später zurückzuholen und eine Methode aufzurufen, um einen skalaren Wert herauszuholen. Stattdessen sollte eine Proxy-Methode erstellt werden, die stattdessen den Skalarwert zurückgibt. Meine Frage ist, warum ist es akzeptabel, ein Objekt in einen Klassenkonstruktor zu übergeben, aber inakzeptabel, das gleiche Objekt später wieder zu bekommen und einen Wert daraus zu ziehen?Gesetz von Demeter und Klassenkonstruktoren
Antwort
Da das Gesetz von Demeter besagt, dass Sie die externe Schnittstelle eines Objekts nicht so entwerfen sollten, dass es aussieht, als ob es aus bestimmten anderen Objekten mit bekannten Schnittstellen besteht, auf die Clients zugreifen können.
Sie übergeben ein Objekt in den Konstruktor, um Ihrem neuen Objekt mitzuteilen, wie es sich verhält, aber es geht Sie nichts an, ob das Objekt dieses Parameterobjekt beibehält oder eine Kopie davon behält oder es nur einmal betrachtet vergisst, dass es jemals existiert hat. Mit einer getMyParameterBack-Methode haben Sie alle zukünftigen Implementierungen festgeschrieben, um das gesamte Objekt bei Bedarf erzeugen zu können, und alle Clients müssen mit zwei statt mit einer Schnittstelle gekoppelt sein. Wenn Sie beispielsweise einen URL-Parameter an den Konstruktor Ihres HTTPRequest-Objekts übergeben, bedeutet dies nicht, dass HTTPRequest eine getURL-Methode haben sollte, die ein URL-Objekt zurückgibt, von dem der Aufrufer dann getProtocol, getQueryString, erwartet. usw. Wenn jemand, der ein HTTPRequest-Objekt hat, das Protokoll der Anfrage wissen möchte, sollte er dies tun (das Gesetz sagt), indem er getProtocol auf das Objekt aufruft, nicht auf ein anderes Objekt, von dem sie wissen, dass HTTPRequest es speichert im Inneren.
Die Idee ist, die Kopplung zu reduzieren - ohne das Gesetz von Demeter muss der Benutzer die Schnittstelle zu HTTPRequest und URL kennen, um das Protokoll zu erhalten. Mit dem Gesetz benötigen sie nur die Schnittstelle zu HTTPRequest. Und HTTPRequest.getProtocol() kann eindeutig "http" zurückgeben, ohne dass ein URL-Objekt in die Diskussion einbezogen werden muss.
Die Tatsache, dass manchmal der Benutzer des Anfrageobjekts derjenige ist, der es erstellt hat und daher auch die URL-Schnittstelle verwendet, um den Parameter zu übergeben, ist weder hier noch dort. Nicht alle Benutzer von HTTPRequest-Objekten haben sie selbst erstellt. Clients, die nach dem Gesetz berechtigt sind, auf die URL zuzugreifen, weil sie sie selbst erstellt haben, können dies auf diese Weise tun, anstatt sie von der Anfrage zurückzuziehen. Clients, die die URL nicht erstellt haben, können dies nicht tun.
Persönlich halte ich das Gesetz von Demeter, wie es in der Regel in einfacher Form angegeben ist, geknackt. Sagen sie ernsthaft, dass, wenn mein Objekt ein String-Namensfeld hat und ich wissen möchte, ob der Name Nicht-ASCII-Zeichen enthält, muss ich entweder eine NameContainsNonASCIICharacters-Methode für mein Objekt definieren, anstatt die Zeichenfolge selbst oder sonst etwas zu betrachten Fügen Sie der Klasse, die eine Callback-Funktion verwendet, eine visitName-Funktion hinzu, um die Einschränkung zu umgehen, indem Sie sicherstellen, dass die Zeichenfolge ein Parameter für eine Funktion ist, die ich geschrieben habe? Das ändert nichts an der Kopplung, es ersetzt nur Getter-Methoden durch Besucher-Methoden. Sollte jede Klasse, die eine ganze Zahl zurückgibt, einen vollständigen Satz arithmetischer Operationen haben, falls ich den Rückgabewert manipulieren möchte? getPriceMultipliedBy (int n)? Sicher nicht.
Was es für nützlich ist, ist, dass, wenn Sie es brechen, Sie sich fragen können, warum Sie es brechen, und ob Sie eine bessere Schnittstelle entwerfen könnten, indem Sie es nicht brechen. Häufig können Sie das, aber es hängt wirklich davon ab, von welchen Arten von Objekten Sie sprechen. Bestimmte Schnittstellen können sicher mit großen Teilen des Codes verbunden werden - Dinge wie Integer, String und sogar URL, die weit verbreitete Konzepte darstellen.
Sehen Sie, ein Uri-Wrapper ist genau die Art von ubiquitärer Klasse, die ich von dieser Regel ausnehmen würde, genau wie eine Zeichenfolge. Als Mittel zum Zweck gesehen, ist dies eine gute Faustregel, aber es ist kein Selbstzweck. Das Ziel ist es, interne Implementierungsdetails zu verstecken, nicht um zu spielen. –
Ja, ich hätte überhaupt keinen Streit, wenn es die Richtlinie von Demeter genannt würde, aber es ist sinnlos, wenn man versucht, Herdenkatzen zu beraten, Programmierer. Ich habe keinen Zweifel, dass es als akademische Übung interessant und fruchtbar ist, sie als Gesetz anzuwenden und viele Ideen zur Verbesserung des Interfacedesigns zu geben. IMO zeichnen Sie die Linie, sobald Sie routinemäßig eine Schnittstelle in eine andere einbetten. Manchmal hat ein X tatsächlich nur ein Y, und jeder weiß es :-) –
Gut gesagt. Meine Sorge über wahnsinnige Katzen typische Programmierer ist, dass einige eine Tendenz haben, gute Ideen als magisch anzusehen. Das stereotypische Beispiel ist der jüngere Absolvent, der das GoF-Buch über Design Patterns liest und dann ALLES in einen Singleton verwandelt. –
Die Idee ist, dass Sie nur mit Ihren unmittelbaren Freunden sprechen. Also, Sie tun dies nicht ...
var a = new A();
var x = a.B.doSomething();
Stattdessen Sie tun dies ...
var a = new A();
var x = a.doSomething(); // where a.doSomething might call b.doSomething();
Es hat seine Vorteile, wie die Dinge einfacher geworden für Anrufer (Car.Start() im Vergleich zu Car.Engine.Start()), aber Sie erhalten viele kleine Verpackungsmethoden. Sie können auch die Mediator pattern verwenden, um diese Art von "Verletzung" zu mildern.
Ich habe noch nie zuvor vom Demeter-Gesetz gehört, also ... macht es Sinn, dass Convenience-Funktionen, die an B delegieren, erforderlich sind, aber "B getB() const" verboten ist? Wenn ja warum? –
Das ist die ganze Idee des Gesetzes ... es könnte neu formuliert werden "Sie sprechen nur mit Ihren unmittelbaren Freunden." Was ist der Vorteil? Diese Idee gibt es schon lange. Heutzutage schreiben wir natürlich sehr lose gekoppelte Anwendungen. –
Die Antwort von JP ist ziemlich gut, also ist dies nur eine Ergänzung, keine Meinungsverschiedenheit oder ein anderer Ersatz.
Die Art, wie ich diese Heuristik verstehe, ist, dass ein Aufruf an A nicht brechen sollte, weil Klasse B sich ändert. Wenn Sie also Ihre Aufrufe mit a.b.foo() verketten, wird die Schnittstelle von A abhängig von B und verletzt die Regel. Stattdessen sollten Sie eine.BFoo() aufrufen, die b.foo() für Sie aufruft.
Dies ist eine gute Faustregel, aber es kann zu umständlichem Code führen, der die Abhängigkeit nicht so sehr anspricht, als ihn zu verankern. Jetzt muss A BFoo für immer anbieten, auch wenn B Foo nicht mehr anbietet. Nicht viel von einer Verbesserung und es wäre wohl zumindest in einigen Fällen besser, wenn Änderungen an B den Anrufer, der Foo will, und nicht B selbst brechen.
Ich würde auch hinzufügen, dass streng genommen diese Regel für eine bestimmte Gruppe von ubiquitären Klassen wie String ständig gebrochen wird. Vielleicht ist es akzeptabel zu entscheiden, welche Klassen in einer bestimmten Schicht einer Anwendung ebenfalls allgegenwärtig sind und Demeters "Regel" für sie frei zu ignorieren.
Irgendwie muss ich dir hier +1 geben, da du das selbe wie mich nur kürzer und schneller gesagt hast :-) –
Amd ich habe dir den Gefallen zurückgegeben, da du ein schönes Beispiel mitgenommen hast. –
Können Sie einen Link zum "Gesetz des Durchmessers" bereitstellen? – gahooa