Ich lese ein Buch mit dem Titel Rails AntiPatterns und sie sprechen über die Verwendung von Delegationen, um das Gesetz von Demeter nicht zu brechen. Hier ist ihr Paradebeispiel:
Sie glauben, dass es schlecht ist, so etwas im Controller aufzurufen (und ich stimme zu)
@street = @invoice.customer.address.street
Ihre vorgeschlagene Lösung besteht darin, Folgendes zu tun:
class Customer
has_one :address
belongs_to :invoice
def street
address.street
end
end
class Invoice
has_one :customer
def customer_street
customer.street
end
end
@street = @invoice.customer_street
Sie behaupten, dass Sie hier nicht gegen das Gesetz von Demeter verstoßen, da Sie nur einen Punkt verwenden. Ich denke, das ist falsch, weil Sie immer noch durch Kunden gehen, um die Adresse durchzugehen, um die Straße der Rechnung zu erhalten. Diese Idee kam mir vor allem in einem Blogbeitrag, den ich las:
http://www.dan-manges.com/blog/37
Im Blogpost ist das Paradebeispiel
class Wallet
attr_accessor :cash
end
class Customer
has_one :wallet
# attribute delegation
def cash
@wallet.cash
end
end
class Paperboy
def collect_money(customer, due_amount)
if customer.cash < due_ammount
raise InsufficientFundsError
else
customer.cash -= due_amount
@collected_amount += due_amount
end
end
end
Der Blogbeitrag besagt, dass dieser Code , obwohl es nur einen Punkt customer.cash
stattdessen gibt customer.wallet.cash
, immer noch gegen das Gesetz von Demeter verstößt.
Jetzt haben wir in der Paperboy-Methode collect_money nicht zwei Punkte, sondern nur einen in "customer.cash". Hat diese Delegation unser Problem gelöst? Überhaupt nicht. Wenn wir uns das Verhalten ansehen, greift ein Zeitungsjunge immer noch direkt in die Brieftasche eines Kunden, um Bargeld herauszuholen.
BEARBEITEN
Ich verstehe und bin damit einverstanden, dass dies immer noch ein Verstoß ist und ich muss eine Methode Wallet
namens "Zurückziehen" erstellen , die die Zahlung für mich abwickelt und die ich innerhalb der Customer
Klasse aufrufen sollte . Was ich nicht verstehe, ist, dass nach diesem Verfahren mein erstes Beispiel immer noch gegen das Gesetz von Demeter verstößt, weil Invoice
es immer noch direkt hineinreicht Customer
, um auf die Straße zu gelangen.
Kann mir jemand helfen, die Verwirrung zu beseitigen? Ich habe in den letzten zwei Tagen nach Informationen gesucht, um dieses Thema aufzugreifen, aber es ist immer noch verwirrend.
quelle
Antworten:
Ihr erstes Beispiel verstößt nicht gegen das Gesetz von Demeter. Ja, mit dem Code , wie es steht, sagen
@invoice.customer_street
geschieht das Gleiche bekommen Wert , dass eine hypothetische@invoice.customer.address.street
würde, aber bei jedem Schritt des Traversal, kehrte der Wert durch das Objekt aufgefordert , entschieden wird - es ist nicht , dass „die paperboy greift in die Kundengeldbörse ", so heißt es," der Zeitungsjunge bittet den Kunden um Bargeld, und der Kunde bekommt das Bargeld von seiner Geldbörse ".Wenn Sie sagen
@invoice.customer.address.street
, gehen davon aus Sie Kenntnis von Kunden- und Adress Interna - dies die schlechte Sache ist. Wenn Sie sagen@invoice.customer_street
, Sie fragen dieinvoice
, "Hey, ich möchte die Straße des Kunden, Sie entscheiden, wie Sie es bekommen ". Der Kunde sagt dann zu seiner Adresse: "Hey, ich möchte deine Straße, du entscheidest, wie du sie bekommst. "Der Schub von Demeter ist nicht "Sie können niemals Werte von Objekten erkennen, die weit entfernt in der Grafik liegen", sondern "Sie selbst dürfen nicht weit entlang der Objektgrafik wandern, um Werte zu erhalten".
Ich stimme zu, dass dies eine subtile Unterscheidung sein mag, aber bedenken Sie Folgendes: Wie viel Code muss sich in Demeter-konformem Code ändern, wenn sich die interne Darstellung eines Codes
address
ändert? Was ist mit nicht Demeter-konformem Code?quelle
Das erste Beispiel und das zweite sind eigentlich nicht sehr gleich. Während das erste über die allgemeinen Regeln von "one dot" spricht, spricht das zweite mehr über andere Dinge im OO-Design, insbesondere " Tell, Don't ask ".
Wieder „ nur für das Verhalten, nicht für Attribute “
Wenn Sie nach Attributen fragen, sollten Sie fragen . "Hey, Mann, wie viel Geld hast du in der Tasche? Zeig mir, ich werde abwägen, ob du das bezahlen kannst." Das ist falsch, kein Einkaufsbeamter wird sich so verhalten. Stattdessen werden sie sagen: "Bitte zahlen"
Es liegt in der Verantwortung des Kunden, zu bewerten, ob er zahlen sollte und ob er zahlen kann. Und die Aufgabe des Angestellten ist erledigt, nachdem der Kunde zum Bezahlen aufgefordert wurde.
Beweist das zweite Beispiel also, dass das erste falsch ist?
Meiner Meinung nach. Nein , solange:
1. Du machst es mit Selbstbeschränkung.
Während Sie auf alle Attribute des Kunden
@invoice
delegiert zugreifen können , ist dies im Normalfall selten erforderlich.Stellen Sie sich eine Seite mit einer Rechnung in einer Rails-App vor. Oben befindet sich ein Abschnitt, in dem die Kundendaten angezeigt werden. Werden Sie in der Rechnungsvorlage so codieren?
Das ist falsch und ineffizient. Ein besserer Ansatz ist
Lassen Sie dann den Kunden teilweise alle Attribute verarbeiten, die dem Kunden gehören.
Also brauchst du das im Allgemeinen nicht. Möglicherweise haben Sie jedoch eine Listenseite, auf der alle aktuellen Rechnungen aufgeführt sind. In jedem Feld wird
li
der Name des Kunden angezeigt . In diesem Fall müssen Sie das Attribut des Kunden anzeigen, und es ist absolut legitim, die Vorlage als zu codieren2. Abhängig von diesem Methodenaufruf erfolgt keine weitere Aktion.
Im obigen Fall der Listenseite wurde auf der Rechnung das Namensattribut des Kunden abgefragt, der eigentliche Zweck ist jedoch " Zeigen Sie mir Ihren Namen ". Es handelt sich also im Grunde immer noch um ein Verhalten, aber nicht um ein Attribut . Es gibt keine weitere Bewertung und Aktion basierend auf diesem Attribut wie, wenn Ihr Name "Mike" ist, werde ich Sie mögen und Ihnen 30 Tage mehr Kredit geben. Nein, auf der Rechnung steht nur "Zeig mir deinen Namen", nicht mehr. Das ist also gemäß der Regel "Tell Don't Ask" in Beispiel 2 völlig akzeptabel.
quelle
Lesen Sie weiter im zweiten Artikel und ich denke, die Idee wird klarer. Die Idee ist nur, dass der Kunde eine Zahlungsmöglichkeit anbietet und vollständig verbirgt, wo der Koffer aufbewahrt wird. Ist es ein Feld, ein Mitglied einer Brieftasche oder etwas anderes? Der Aufrufer weiß es nicht, muss es nicht wissen und ändert sich nicht, wenn sich das Implementierungsdetail ändert.
Ich denke, Ihre zweite Referenz gibt eine hilfreichere Empfehlung.
Die "Ein-Punkt" -Idee ist insofern ein Teilerfolg, als sie einige tiefe Details verbirgt, aber die Kopplung zwischen getrennten Komponenten noch verstärkt.
quelle
Klingt, als hätte Dan sein Beispiel aus diesem Artikel abgeleitet: The Paperboy, The Wallet und The Law Of Demeter
Ich verstehe, dass Ihr Beispiel in Ruby ist, aber dies sollte für alle OOP-Sprachen gelten.
quelle