Darf eine Klasse nach Demeters Gesetz eines ihrer Mitglieder zurückgeben?


13

Ich habe drei Fragen zum Demeter-Gesetz.

Abgesehen von Klassen, der Rückkehr Objekten speziell ernannt wurden - wie Fabrik und Erbauer Klassen - ist es in Ordnung , für eine Methode ein Objekt zurück, zB ein Objekt gehalten durch eine der Eigenschaften der Klasse oder würde , die verletzen das Gesetz des demeter (1) ? Und wenn es gegen das Gesetz der Demeter verstößt, wäre es dann von Bedeutung, wenn das zurückgegebene Objekt ein unveränderliches Objekt ist, das ein Datenelement darstellt und nur Getter für diese Daten enthält (2a)? Oder ist solch ein ValueObject ein Anti-Pattern an sich, weil alles, was mit den Daten in dieser Klasse gemacht wird, außerhalb der Klasse gemacht wird (2b)?

Im Pseudocode:

class A {}

class B {

    private A a;

    public A getA() {
        return this.a;
    }
}

class C {

    private B b;
    private X x;

    public void main() {
        // Is it okay for B.getA to exist?
        A a = this.b.getA();

        a.doSomething();

        x.doSomethingElse(a);
    }
}

Ich vermute, dass Demeters Gesetz ein Muster wie das obige verbietet. Was kann ich tun, um sicherzustellen, dass doSomethingElse()bei Nichtbeachtung des Gesetzes (3) angerufen werden kann?


9
Viele Leute (insbesondere funktionierende Programmierer) betrachten das Gesetz von Demeter als Anti-Muster. Andere sehen darin den "gelegentlich nützlichen Vorschlag von Demeter".
David Hammen

1
Ich werde diese Frage aufgreifen, weil sie die Absurditäten des Gesetzes von Demeter illustriert. Ja, LoD ist "gelegentlich nützlich", aber normalerweise dumm.
user949300

Wie wird xes gemacht?
candied_orange

@CandiedOrange xist nur eine andere Eigenschaft, deren Wert nur ein Objekt ist, das aufgerufen werden kann doSomethingElse.
user2180613

1
Ihr Beispiel verstößt nicht gegen LOD. Bei LOD geht es darum, zu verhindern, dass Clients eines Objekts interne Details oder Mitbearbeiter mit diesem Objekt verbinden. Sie möchten Dinge wie vermeiden user.currentSession.isLoggedIn(). weil es Paare Kunden usernicht nur , usersondern auch seine Mitarbeiter, session. Stattdessen möchten Sie schreiben können user.isLoggedIn(). Dies wird normalerweise durch Hinzufügen einer isLoggedInMethode erreicht, an userderen Implementierung delegiert wird currentSession.
Jonah

Antworten:


7

In vielen Programmiersprachen sind alle zurückgegebenen Werte Objekte. Wie andere bereits gesagt haben, zwingt Sie die Unfähigkeit, die Methoden von zurückgegebenen Objekten zu verwenden, dazu, niemals etwas zurückzugeben. Sie sollten sich fragen: "Was sind die Aufgaben der Klassen A, B und C?" Aus diesem Grund fordern metasyntaktische Variablennamen wie A, B und C immer die Antwort "es hängt davon ab" an, da in diesen Begriffen keine erblichen Verantwortlichkeiten enthalten sind.

Vielleicht möchten Sie sich mit domänengetriebenem Design befassen, das Ihnen differenziertere Heuristiken bietet, um zu überlegen, wohin die Funktionalität gehen soll und wer was aufrufen soll.

Ihre zweite Frage zu unveränderlichen Objekten bezieht sich auf den Begriff eines
Wertobjekts im Vergleich zu einem Entitätsobjekt. In DDD können Sie Wertobjekte nahezu uneingeschränkt weitergeben. Dies gilt nicht für Entitätsobjekte.

Die vereinfachte LOD wird in DDD viel besser ausgedrückt als die Regeln für den Zugriff auf Aggregate . Keine externen Objekte sollten Verweise auf Mitglieder von Aggregaten enthalten. Es sollte nur ein Stammverweis gespeichert werden.

Abgesehen von DDD möchten Sie zumindest eigene Gruppen von Stereotypen für Ihre Klassen entwickeln, die für Ihre Domain angemessen sind. Erzwingen Sie beim Entwerfen Ihres Systems Regeln zu diesen Stereotypen.

Denken Sie auch immer daran, dass alle diese Regeln dem Umgang mit Komplexität dienen und nicht dazu, sich selbst zu behindern.


1
Gibt DDD Regeln an, wann Klassenmitglieder wie Daten verfügbar gemacht werden können (dh, es sollte Getter geben)? Alle Diskussionen um Law of Demeter, tell, don't ask, getters are evil, object encapsulationund single responsibility principlescheinen auf diese Frage einkochen: wann sollte eine Delegation zu einem Domänenobjekt verwendet werden , im Gegensatz zu dem Objektwert bekommen und mit ihm etwas zu tun? Es wäre sehr nützlich, differenzierte Qualifikationen auf Situationen anwenden zu können, in denen eine solche Entscheidung getroffen werden muss.
user2180613

Richtlinien wie "Getter sind böse" existieren, weil viele Leute Klasseninstanzen als Datenstrukturen behandeln (Ein Beutel mit Daten, der weitergegeben werden muss). Dies ist keine objektorientierte Programmierung. Objekte sind Bündel von Funktionen / Verhaltensweisen , die tatsächlich Arbeit leisten. Der Arbeitsumfang wird durch die Verantwortung bestimmt. Diese Arbeit wird offensichtlich Objekte erstellen, die von Methoden usw. zurückgegeben werden. Wenn Ihre Klasse tatsächlich sinnvolle Arbeit leistet, die Sie klar über "Datenobjekt X mit Attributen Y und Z darstellen" hinaus definieren können, sind Sie auf dem richtigen Weg . Ich würde nicht darüber streiten.
Jeremy

Wenn Sie in diesem Punkt viele Getter / Asker verwenden, bedeutet dies normalerweise, dass Sie irgendwo eine große Methode haben, die alles orchestriert, was Fowler ein Transaktionsskript nennt. Bevor Sie beginnen, einen Getter zu verwenden, fragen Sie sich, warum ich diese Daten vom Objekt erbitte. Warum ist diese Funktionalität nicht in einer Methode für das Objekt enthalten? Wenn es nicht in der Verantwortung dieser Klasse liegt, diese Funktionalität zu implementieren, verwenden Sie einen Getter. Das Buch [Refactoring] ( martinfowler.com/books/refactoring.html ) von Fowler ist ein Heuristikbuch für solche Fragen.
Jeremy

In seinem Buch Implementing Domain-Driven Design erwähnt Vaughn Vernon in Kapitel 10, S. 382, ​​das Gesetz von Demeter und Tell, Don't Ask.
Jeremy

6

Darf eine Klasse nach Demeters Gesetz eines ihrer Mitglieder zurückgeben?

Ja, das ist es mit Sicherheit.

Schauen wir uns die wichtigsten Punkte an :

  • Jede Einheit sollte nur begrenzte Kenntnisse über andere Einheiten haben: nur Einheiten, die "eng" mit der aktuellen Einheit verbunden sind.
  • Jede Einheit sollte nur mit ihren Freunden sprechen. Sprich nicht mit Fremden.
  • Sprechen Sie nur mit Ihren direkten Freunden.

Alle drei lassen Sie eine Frage stellen: Wer ist ein Freund?

Bei der Entscheidung, was zurückgegeben werden soll, schreibt das Gesetz von Demeter oder das Prinzip des geringsten Wissens (LoD) nicht vor, dass Sie sich gegen Programmierer verteidigen, die darauf bestehen, gegen das Gesetz zu verstoßen. Es schreibt vor, dass Sie Codierer nicht dazu zwingen, dagegen zu verstoßen.

Diese zu verwirren ist genau der Grund, warum so viele glauben, ein Setter müsse immer ins Leere gehen. Nein. Sie müssen die Möglichkeit einräumen, Abfragen (Getter) durchzuführen, die den Status des Systems nicht ändern. Das ist die grundlegende Trennung von Befehlsabfragen .

Bedeutet dies, dass Sie sich frei in eine Codebasis vertiefen können, die alles miteinander verkettet, was Sie wollen? Verketten Sie nur das, was zusammen verkettet werden sollte. Andernfalls könnte sich die Kette ändern und plötzlich ist Ihr Zeug kaputt. Dies ist, was mit Freunden gemeint ist.

Lange Ketten können für ausgelegt werden. Fließende Schnittstellen, iDSL, ein Großteil von Java8 und der gute alte StringBuilder ermöglichen den Aufbau langer Ketten. Sie verletzen LoD nicht, weil alles in der Kette zusammenarbeiten soll und versprochen wird, weiter zusammenzuarbeiten. Sie verletzen Demeter, wenn Sie Dinge aneinander reihen, die noch nie voneinander gehört haben. Freunde sind diejenigen, die versprochen haben, Ihre Kette am Laufen zu halten. Freunde von Freunden nicht.

Abgesehen von Klassen, die speziell für die Rückgabe von Objekten bestimmt sind - wie Factory- und Builder-Klassen - ist es in Ordnung, wenn eine Methode ein Objekt zurückgibt, z. ?

Dies schafft nur die Möglichkeit, gegen Demeter zu verstoßen. Dies ist keine Verletzung. Das ist nicht unbedingt schlecht.

Und wenn es gegen das Demeter-Gesetz verstößt, wäre es dann von Bedeutung, wenn das zurückgegebene Objekt ein unveränderliches Objekt ist, das ein Datenelement darstellt und nur Getter für diese Daten enthält (2)?

Unveränderlich ist hier gut, aber irrelevant. Dinge durch eine längere Kette zu erreichen, macht es nicht besser. Was es besser macht, ist das Trennen des Erhaltens vom Verwenden. Wenn Sie verwenden, fragen Sie nach dem, was Sie als Parameter benötigen. Gehen Sie nicht auf die Jagd, indem Sie sich mit Fremden befassen.

Im Pseudocode:

Ich vermute, dass Demeters Gesetz ein Muster wie das obige verbietet. Was kann ich tun, um sicherzustellen, dass doSomethingElse () aufgerufen werden kann, ohne das Gesetz zu verletzen (3)?

Bevor ich darüber x.doSomethingElse(a)spreche, verstehe ich , dass du grundlegend geschrieben hast

b.getA().doSomething()

Jetzt ist LoD keine Punktezählübung . Aber wenn Sie eine Kette erstellen, sagen Sie, dass Sie wissen, wie man eine A(mit B) erhält und wie man eine verwendet A. Nun gut Aund Bbesser enge Freunde sein, weil du sie einfach zusammengekoppelt hast.

Wenn Sie nur darum gebeten hätten, Ihnen etwas AÄhnliches zu geben, das Sie verwenden könnten, Aund es hätte Sie nicht interessiert, woher es kommt, und Sie Bkönnten ein Leben führen, das glücklich und frei von Ihrer Obsession ist, Adavon zu leben B.

Was die Herkunft x.doSomethingElse(a)ohne Details xangeht, hat LoD überhaupt nichts zu sagen.

LoD kann die Trennung von Nutzung und Konstruktion fördern . Aber ich werde darauf hinweisen, wenn Sie jedes Objekt religiös als nicht freundlich behandeln, werden Sie nicht in der Lage sein, Code in statischen Methoden zu schreiben. Sie können auf diese Weise einen wunderbar komplexen Objektgraphen erstellen, aber irgendwann müssen Sie eine Methode darauf aufrufen, um das Ding zum Laufen zu bringen. Sie müssen sich nur entscheiden, wer Ihre Freunde sind. Daran führt kein Weg vorbei.

Ja, eine Klasse darf eines ihrer Mitglieder unter LoD zurückgeben. Wenn Sie dies tun, sollten Sie klarstellen, ob dieses Mitglied mit Ihrer Klasse befreundet ist. Andernfalls versucht ein Client möglicherweise, Sie an diese Klasse zu koppeln, indem Sie Sie verwenden, um sie abzurufen, bevor Sie sie verwenden. Das ist wichtig, denn jetzt muss das, was Sie zurückgeben, diese Verwendung immer unterstützen.

Es gibt viele Fälle, in denen dies einfach kein Problem darstellt. Sammlungen können dies einfach ignorieren, weil sie mit allem, was sie verwendet, befreundet sein sollen. Ähnlich wertvolle Objekte sind mit jedem freundlich. Wenn Sie jedoch ein Dienstprogramm zur Adressvalidierung geschrieben haben, das ein Mitarbeiterobjekt anfordert, aus dem eine Adresse extrahiert wurde, und dann nur nach der Adresse fragen, sollten Sie hoffen, dass sowohl Mitarbeiter als auch Adresse aus derselben Bibliothek stammen.


Fließende Interfaces sind keine Ausnahme von LOD, da sie sich alle selbst zurückgeben. In einer fließenden Schnittstelle wird jede der Methoden in der Kette für dasselbe Objekt aufgerufen. settings.darkTheme().monospaceFont()ist nur syntaktischer Zucker fürsettings.darkTheme(); settings.monospaceFont();
Jonah

3
@Jonah Returning Self ist die ultimative Freundlichkeit. Und nicht alle fließenden Schnittstellen. Viele iDSLs sind auch fließend und geben unterschiedliche Typen zurück, um die verfügbaren Methoden an verschiedenen Punkten zu ändern. Betrachten Sie JOOQ , Step Builder , Wizard Builder und meinen eigenen Albtraum- Monster-Builder . Fließend ist ein Stil. Kein Muster.
candied_orange

2

Abgesehen von Klassen, die speziell für die Rückgabe von Gegenständen eingerichtet wurden [...]

Ich verstehe dieses Argument nicht ganz. Jedes Objekt kann ein Objekt als Ergebnis eines Nachrichtenaufrufs zurückgeben. Vor allem, wenn Sie an "alles als Objekt" denken.

Klassen sind jedoch die einzigen Objekte, die neue Objekte ihrer Art instanziieren können, indem sie ihnen die "neue" Nachricht senden (oder in den meisten gängigen OOP-Sprachen häufig durch ein neues Schlüsselwort ersetzt werden).


Was Ihre Frage betrifft, wäre ich ziemlich misstrauisch, wenn ein Objekt eine seiner Eigenschaften zurückgibt, um anderweitig verwendet zu werden. Es könnte sein, dass dieses Objekt Informationen über seinen Status oder seine Implementierungsdetails preisgibt.

Und Sie möchten nicht, dass das C-Objekt die Implementierungsdetails von B kennt. Dies ist eine unerwünschte Abhängigkeit vom A-Objekt aus der C-Objektperspektive.

Vielleicht möchten Sie sich fragen, ob C, wenn es A kennt, nicht zu viel weiß für die Arbeit, die es unabhängig von der LOD zu tun hat.

Es gibt Fälle, in denen dies legitim sein kann, z. B. wenn ein Erfassungsobjekt nach einem seiner Elemente gefragt wird und keine Demeter-Regel verletzt wird. Es ist riskant und sollte vermieden werden, ein Book-Objekt nach seinem SQLConnection-Objekt zu fragen.

Die LOD existiert, weil viele Programmierer dazu neigen, Objekte nach Informationen zu fragen, bevor sie eine Arbeit daraus ausführen. LOD ist da, um uns daran zu erinnern, dass wir manchmal (wenn nicht immer) die Dinge nur überkomplizieren, indem wir wollen, dass unsere Objekte zu viel bewirken. Manchmal müssen wir nur einen anderen Gegenstand bitten, die Arbeit für uns zu erledigen. LOD ist da, um uns Gedanken darüber zu machen, wo wir das Verhalten in unsere Objekte einfügen.


0

Abgesehen von Klassen, die speziell für die Rückgabe von Objekten eingerichtet wurden (z. B. Factory- und Builder-Klassen), ist es für eine Methode in Ordnung, ein Objekt zurückzugeben?

Warum sollte es nicht so sein? Sie scheinen vorzuschlagen, dass Sie Ihren Programmiererkollegen signalisieren müssen, dass die Klasse speziell zum Zurückgeben von Objekten gedacht ist, oder dass sie überhaupt keine Objekte zurückgeben darf. In Sprachen, in denen alles ein Objekt ist, würde dies Ihre Optionen erheblich einschränken, da Sie überhaupt nichts zurückgeben können.


Das Beispiel handelt von dem impliziten b.getA().doSomething()undx.doSomethingElse(b.getA())
Benutzer 2180613

A a = b.getA(); a.DoSomething();- Zurück zu einem Punkt.
Robert Harvey

2
@ user2180613 - Wenn Sie danach fragen wollten b.getA().doSomething()und x.doSomethingElse(b.getA())Sie explizit danach hätten fragen sollen. Wie es aussieht, scheint Ihre Frage zu sein this.b.getA().
David Hammen

1
Da Aes kein Mitglied von ist C, in dem es nicht erstellt wurde Cund für das es nicht bereitgestellt wird C, scheint es, dass die Verwendung Ain C(auf welche Weise auch immer) gegen die LoD verstößt. Punkte zählen ist nicht das, worum es bei LoD geht, es ist nur ein anschauliches Werkzeug. Es ist möglich, Driver().getCar().getSteeringWheel().getFrontWheels().toRight()Anweisungen, die jeweils einen Punkt enthalten , in separate Anweisungen umzuwandeln , aber die Verletzung von LoD ist weiterhin vorhanden. Ich habe in vielen Beiträgen in diesem Forum gelesen, dass das Ausführen von Aktionen an das Objekt delegiert werden sollte, das das tatsächliche Verhalten implementiert, d tell don't ask. H. Die Rückgabe von Werten scheint dem zu widersprechen.
user2180613

1
Ich nehme an, das ist wahr, aber es beantwortet die Frage nicht.
user2180613

0

Kommt darauf an, was du tust. Wenn Sie ein Mitglied Ihrer Klasse aussetzen, wenn es niemanden interessiert, dass es sich um ein Mitglied Ihrer Klasse handelt, oder um den Typ dieses Mitglieds, ist das eine schlechte Sache. Wenn Sie dann den Typ dieses Members und den Typ der Funktion ändern, die das Member zurückgibt, und jeder seinen Code ändern muss, ist das böse.

Wenn die Schnittstelle Ihrer Klasse dagegen angibt, dass sie eine Instanz einer bekannten Klasse A liefert, ist das in Ordnung. Wenn Sie Glück haben und dies tun können, indem Sie ein Mitglied Ihrer Klasse ausstatten, gut zu Ihnen. Wenn Sie dieses Mitglied von A in B geändert haben, müssen Sie die Methode, die ein A zurückgibt, neu schreiben. Offensichtlich muss nun eine Methode erstellt und mit verfügbaren Daten gefüllt werden, anstatt nur ein einziger Zeilenumbruch ein Mitglied. Die Schnittstelle Ihrer Klasse wäre unverändert.

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.