2013-03-28 9 views
11

Ich habe fast alle Fragen mit dem Tag des Demeter gelesen. Meine spezifische Frage wird in keiner dieser anderen Fragen beantwortet, obwohl sie sehr ähnlich ist. Hauptsächlich meine Frage ist, wenn Sie ein Objekt mit Kompositionsschichten haben, aber die Eigenschaftswerte von den verschiedenen Objekten abrufen müssen, wie erreichen Sie das und warum gehen Sie einen Ansatz vor einen anderen?Wie arbeiten das Demeter-Gesetz und die Komposition mit Sammlungen zusammen?

Angenommen, Sie haben ein ziemlich Standard-Objekt haben von anderen Objekten zusammengesetzt, wie folgt aus:

public class Customer { 
    private String name; 
    private ContactInfo primaryAddress; 
    private ContactInfo workAddress; 
    private Interests hobbies; 
    //Etc... 

    public getPrimaryAddress() { return primaryAddress; } 
    public getWorkAddress() { return workAddress; } 
    public getHobbies() { return hobbies; } 
    //Etc... 
} 

private ContactInfo { 
    private String phoneNumber; 
    private String emailAddress; 
    //Etc... 

    public getPhoneNumber() { return phoneNumber; } 
    public getEmailAddress() { return emailAddress; } 
    //Etc... 
} 

private Interests { 
    private List listOfInterests; 
} 

Die folgende verletzen würde sowohl das Gesetz von Demeter:

System.out.println("Phone: " + customer.getPrimaryAddress().getPhoneNumber()); 
System.out.println("Hobbies: " + customer.getHobbies().getListOfInterests().toString()); 

Dies würde auch das Gesetz verstoßen von Demeter, denke ich (Klarstellung?):

ContactInfo customerPrimaryAddress = customer.getPrimaryAddress(); 
System.out.println("Phone: " + customerPrimaryAddress.getPhoneNumber()); 

Also vermutlich, du wou ld dann fügen Sie ein "getPrimaryPhoneNumber()" Methode Kunde:

public getPrimaryPhoneNumber() { 
    return primaryAddress.getPhoneNumber(); 
} 

Und dann rufen Sie einfach an: System.out.println ("Telefon:" + customer.getPrimaryPhoneNumber());

Aber im Laufe der Zeit scheint es, als würde es tatsächlich eine Menge Probleme bereiten und gegen die Absicht des Demeter-Gesetzes arbeiten. Es macht die Customer-Klasse zu einer riesigen Tüte Getter und Setter, die viel zu viel Wissen über ihre eigenen internen Klassen hat. Zum Beispiel scheint es möglich, dass das Kundenobjekt eines Tages verschiedene Adressen (nicht nur eine "primäre" Adresse und eine "geschäftliche" Adresse) haben wird. Vielleicht wird sogar die Customer-Klasse einfach eine Liste (oder eine andere Sammlung) von ContactInfo-Objekten anstelle von bestimmten benannten ContactInfo-Objekten haben. Wie gehst du in diesem Fall weiterhin dem Demeter-Gesetz nach? Es scheint den Zweck der Abstraktion zu besiegen. Zum Beispiel scheint dies sinnvoll in einem solchen Fall, in dem ein Kunde eine Liste der Contact Artikel hat:

Customer.getSomeParticularAddress(addressType).getPhoneNumber(); 

Dies scheint, wie es noch verrückter bekommen kann, wenn man über einige Leute denken, ein Mobiltelefon und ein Festnetz-Telefon mit, und dann muss ContactInfo eine Sammlung von Telefonnummern haben.

Customer.getSomeParticularAddress(addressType).getSomePhoneNumber(phoneType).getPhoneNumber(); 

In diesem Fall beziehen wir uns nicht nur auf Objekte innerhalb von Objekten innerhalb von Objekten, aber wir müssen auch wissen, was die Gültigkeit des address und Phone die sind. Ich kann definitiv ein Problem mit diesem sehen, aber ich bin nicht sicher, wie man es vermeidet. Vor allem, wenn irgendeine Klasse dies anruft, weiß sie wahrscheinlich, dass sie die "mobile" Telefonnummer für die "primäre" Adresse des Kunden in Frage stellen wollen.

Wie könnte dies umgestaltet werden, um dem Demeter-Gesetz zu entsprechen und warum sollte das gut sein?

+7

ich glaube, Sie es zu wörtlich, es ist nur eine Richtlinie für möglichen Code riecht einnehmen. Was Sie tun, ist in Ordnung. – Esailija

+2

Stimmen Sie mit Estagija überein, das Gesetz des Demeter muss nicht wirklich auf Getters angewendet werden, sie sind zu trivial. Tatsächlich würde das, was Sie vorschlagen (Customer.getPrimaryPhoneNumber()), dazu führen, dass der Kunde die Interna von ContactInfo kennen muss. Der Vorteil von Kompositionstypobjekten besteht darin, dass Sie einschränken, wer über ContactInfo Bescheid wissen muss, z. – Taylor

+0

OK, ich sehe diesen Punkt, aber in meinem Kopf wird es Nicht-Getter/Setter-Szenarien für das Zeug in ContactInfo geben. Angenommen, Sie möchten den Kunden per E-Mail über einen Link oder eine Schaltfläche informieren. Sie könnten es wie customer.getSomeParticularContactInfo (addressType) .sendEmail() oder customer.sendEmail() schreiben, die dann (innerhalb des Kunden) getSomeParticularContactInfo ("primär"). SendEmail() aufruft. Oder es gibt andere Optionen wie eine Manager-Klasse, die die Aktion behandelt. In dem Beispiel geht es dann nicht um Getter oder Setter, und ich bin verwirrt darüber, wie man in diesem Kontext Designentscheidungen trifft. –

Antwort

2

Nach meiner Erfahrung ist das gezeigte Customer kein "Standardobjekt, das aus anderen Objekten besteht", da dieses Beispiel die zusätzlichen Schritte der Implementierung seiner Komponierstücke als innere Klassen und darüber hinaus die Herstellung dieser inneren Klassen als privat betrachtet. Das ist keine schlechte Sache.

Im Allgemeinen erhöht der private Zugriffsmodifikator das Verbergen von Informationen, was die Grundlage für das Demeter-Gesetz bildet. Die Offenlegung privater Klassen ist widersprüchlich. Die NetBeans-IDE enthält eine Standardcompilerwarnung für "Exportieren eines nicht öffentlichen Typs über die öffentliche API".

Ich würde behaupten, dass das Aussetzen einer privaten Klasse außerhalb ihrer einschließenden Klasse immer schlecht ist: sie reduziert das Verstecken von Informationen und verletzt das Gesetz von Demeter. Um die Frage zu beantworten, ob eine Instanz ContactInfo außerhalb von Customer zurückgegeben wird: Ja, das ist ein Verstoß. Die vorgeschlagene Lösung, eine getPrimaryPhoneNumber()-Methode zu Customer hinzuzufügen, ist eine gültige Option. Die Verwirrung ist hier: "Kunde ... hat viel zu viel Wissen über seine eigenen internen Klassen." Das ist nicht möglich; Daher ist es wichtig, dass dieses Beispiel kein Standardzusammensetzungsbeispiel ist.

Eine einschließende Klasse verfügt über 100% Kenntnis aller verschachtelten Klassen. Immer. Unabhängig davon, wie diese verschachtelten Klassen in der einschließenden Klasse (oder anderswo) verwendet werden. Aus diesem Grund hat eine einschließende Klasse direkten Zugriff auf private Felder und Methoden ihrer verschachtelten Klassen: Die einschließende Klasse weiß von Natur aus alles darüber, weil sie darin implementiert ist.

Angesichts der absurden Beispiel einer Klasse Foo, die eine geschachtelte Klasse Bar, die eine geschachtelte Klasse Baz, die eine geschachtelte Klasse Qux hat, hat, wäre es kein Verstoß gegen Demeter for Foo (intern) Call Bar .baz.qux.method(). Foo kennt bereits alles, was es über Bar, Baz und Qux zu wissen gibt; Weil ihr Code innerhalb von Foo liegt, wird kein zusätzliches Wissen über die lange Methodenkette weitergegeben.

Die Lösung dann, nach dem Gesetz von Demeter, ist für Customer keine Zwischenobjekte zurückzugeben, unabhängig von seiner internen Implementierung; d. h. ob Customer unter Verwendung mehrerer verschachtelter Klassen oder keiner implementiert ist, sollte es nur das zurückgeben, was seine Clientklassen letztendlich benötigen.

Zum Beispiel des letzte Codeausschnitt könnte als Refactoring, customer.getPhoneNumber(addressType, phoneType);

oder wenn es nur eine kleine Anzahl von Optionen, customer.getPrimaryMobilePhoneNumber();

Entweder Ansatz führt zu Benutzern der Customer Klasse ist nicht bewusst, seine interne Implementierung und stellt sicher, dass diese Benutzer nicht durch Objekte aufrufen müssen, die sie nicht direkt interessieren.

+0

verstanden: https: //javadevguy.wordpress.com/2017/05/14/Das Genie des Demeter/ – jaco0646

0

Wie wäre es mit einer anderen Klasse mit dem Namen wie CustomerInformationProvider. Sie können Ihr Objekt Customer als Konstruktorparameter an seine neue Klasse übergeben. Und dann können Sie alle spezifischen Methoden schreiben, um Telefone, Adressen usw. innerhalb dieser Klasse zu erhalten, während Sie die Klasse Customer sauber lassen.

+0

Dies ist eine gute Option, aber führen zu in zu viele Provider-Klassen – swapyonubuntu

0

Es ist wichtig, sich daran zu erinnern, dass das Gesetz von Demeter ist, trotz seines Namens, eine Richtlinie und nicht ein tatsächliches Recht. Wir müssen seinen Zweck auf einer etwas tieferen Ebene untersuchen, um zu bestimmen, was hier richtig ist.

Der Zweck des Demeter-Gesetzes besteht darin, zu verhindern, dass Objekte von außen auf die Einbauten eines anderen Objekts zugreifen können. Der Zugriff auf Interna hat zwei Probleme: 1) er gibt zu viele Informationen über die interne Struktur des Objekts und 2) er erlaubt auch externen Objekten, die Interna der Klasse zu modifizieren.

Die richtige Antwort auf dieses Problem ist, das Objekt, das von der Customer-Methode von der internen Darstellung zurückgegeben wird, zu trennen.Mit anderen Worten, anstatt einen Verweis auf das private interne Objekt vom Typ ContactInfo zurückzugeben, definieren wir stattdessen eine neue Klasse UnmodifiableContactInfo, und getPrimaryAddress return UnmodifiableContactInfo, erstellen Sie es und füllen Sie es bei Bedarf.

Dies bringt uns beide die Vorteile des Demeter-Gesetzes. Das zurückgegebene Objekt ist nicht mehr ein internes Objekt des Kunden, was bedeutet, dass der Kunde seinen internen Speicher so oft ändern kann, wie er möchte, und dass nichts, was wir an UnmodiableContactInfo tun, Auswirkungen auf die Interna des Kunden hat.

(In Wirklichkeit würde ich die interne Klasse umbenennen und die äußerlichen als Contact verlassen, aber das ist ein kleiner Punkt)

Also das die Ziele des Gesetzes von Demeter erreicht, aber es sieht immer noch wie wir brechen es. Ich denke, dass die Methode getAddress kein ContactInfo-Objekt zurückgibt, sondern es instanziiert. Das bedeutet, dass wir gemäß den Demeter-Regeln auf die Methoden von ContactInfo zugreifen können und dass der Code, den Sie oben geschrieben haben, kein Verstoß ist.

Sie müssen natürlich beachten, dass, obwohl die "Verletzung des Demeter-Gesetzes" im Code auf den Kunden aufgetreten ist, der Fix muss in Customer gemacht werden. Im Allgemeinen ist die Korrektur eine gute Sache - es ist schlecht, Zugriff auf interne Objekte zu haben, unabhängig davon, ob auf sie mit mehr als einem "Punkt" zugegriffen wird oder nicht.

Ein paar Anmerkungen. Es ist offensichtlich, dass eine über strikte Anwendung des Demeter-Gesetzes zu Dummheiten führt, verbietet zum Beispiel:

int nameLength = myObject.getName().length() 

eine technische Verletzung ist, die die meisten von uns jeden Tag. Jeder tut auch:

mylist.get(0).doSomething(); 

was technisch eine Verletzung ist. Aber die Realität ist, dass keine davon ein Problem darstellt, es sei denn, wir erlauben tatsächlich, dass externer Code das Verhalten des Hauptobjekts (Kunde) basierend auf den Objekten beeinflusst, die er abgerufen hat.

Zusammenfassung

Hier ist, was Ihr Code soll wie folgt aussehen:

public class Customer { 
    private class InternalContactInfo { 
     public ContactInfo createContactinfo() { 
      //creates a ContactInfo based on its own values... 
     } 
     //Etc.... 
    } 
    private String name; 
    private InternalContactInfo primaryAddress; 
    //Etc... 

    public Contactinfo getPrimaryAddress() { 
     // copies the info from InternalContactInfo to a new object 
     return primaryAddress.createContactInfo(); 
    } 
    //Etc... 
} 

public class ContactInfo { 
    // Not modifiable 
    private String phoneNumber; 
    private String emailAddress; 
    //Etc... 

    public getPhoneNumber() { return phoneNumber; } 
    public getEmailAddress() { return emailAddress; } 
    //Etc... 
} 

}