Archive for the ‘Tell don’t ask’ tag
Programmierprinzipien: Law of Demeter
Professor Ian Holland hat zu Beginn der objektorientierten Zeit (1989) bereits eine wichtige Richtlinie definiert, die die lose Kopplung von Klassen sicherstellen soll: Das „Gesetz von Demeter“ (Law of Demeter, LoD).
Wenn man Klassen soweit es geht voneinander trennt, sind sie übersichtlicher, besser wartbar und testbar, leichter weiterzuentwickeln und wiederzuverwenden. Umgangssprachlich könnte man es beschreiben als „Sprich nur mit deinen nächsten Freunden“ und „Verrate keine Geheimnisse, die andere nichts angehen“.
Ein Praxisbeispiel:
class Order { public $orderStatus = 0; public function changeOrderStatus($newStatus, $customer) { $this->orderStatus = $newStatus; if ($newStatus == 3) { $this->sendEmail( $customer->getData()->getContactInformation()->getEmail(), 'New Order Status: ' . $newStatus ); } } }
Wer bei diesem Code keine Bauchschmerzen hat, sollte unbedingt weiterlesen. Wenn wir darauf nun die Regeln des LoD loslassen, sehen wir die Probleme.
Eine Klassenmethode sollte nur folgende andere Methoden verwenden:
- Methoden der eigenen Klasse
- Methoden der übergebenen Parameter
- Methoden der mit eigenen Klasse assoziierten Klassen
- Methoden von Objekten, die die Methode selbst erzeugt hat
Die Zeile 11 wäre also verboten, da sie eine zu enge Kopplung bzw. ein zu großes Wissen über andere Klassen voraussetzt. Lösung wäre hier, nicht das ganze Kundenobjekt an die Methode zu übergeben, sondern nur die für hier wichtigen Kontaktinformationen.
Außerdem verstößt der Code gegen das Geheimnisprinzip, da das Attribut $orderStatus public ist und man so den Status ändern könnte ohne eine Email zu versenden.
Wenn wir einen Test für die oben beschriebene Methode schreiben möchten, müssen wir vorher erst ein Kundenobjekt erzeugen, darin Daten hinterlegen, Kontaktinformationen usw. Doch eigentlich würden die Kontaktinformationen reichen für den Test, das Kundenobjekt ansich ist uns eigentlich egal, da es nicht genutzt wird. Auch Testen wird also durch lose Kopplung einfacher.
Besser wäre z.B. der folgende Code.
class Order { private $_orderStatus = 0; public function changeOrderStatus($newStatus, $customerInformation) { $this->_orderStatus = $newStatus; if ($newStatus == 3) { $this->_sendEmail( $customerInformation->getEmail(), 'New Order Status: ' . $newStatus ); } } }
Oder man macht es wie im unten stehenden Beispiel. Darin ist auch das Prinzip „Tell don’t ask“ abgebildet, welches besagt, dass man lieber Befehle gibt als Informationen abzufragen:
class Order { private $_orderStatus = 0; public function changeOrderStatus($newStatus, $customer) { $this->_orderStatus = $newStatus; if ($newStatus == 3) { $customer->sendEmail('New Order Status: ' . $newStatus); } } }
Wir geben also dem Kundenobjekt den Befehl, eine Email zu versenden (an den Kunden). Dann ersparen wir uns das Abfragen von Informationen, und wir müssen nicht wissen, wie im Kundenobjekt die Email-Adresse abgespeichert wird. Änderungen der Kundenklasse sind so also viel einfacher und problemloser machbar. Die Klasse Order kümmert sich also hauptsächlich um seine eigenen Dinge, und überlässt alles was den Kunden betrifft wenn es geht der Kundenklasse. Stichwort ist da das Single Responsibility Principle bzw. das „Eine-Verantwortlichkeit-Prinzip„.
Um nochmal schnell die 4 Prinzipien an Code darzustellen, hier die erlaubten Methodenaufrufe:
Methoden der eigenen Klasse:
class A { public function method1() { $this->method2(); } public function method2() { } }
Methoden der Parameter:
class A { public function method1(B $b) { $b->method2(); } } class B { public function method2() { } }
Methoden assoziierter Klassen:
class A { private $b; public function method1() { $this->b->method2(); } } class B { public function method2() { } }
Methoden selbst erzeugter Objekte:
class A { public function method1() { $b = new B(); $b->method2(); } } class B { public function method2() { } }
Weitere Informationen zum LoD gibts im deutschen oder englischen Artikel in der Wikipedia. Oder direkt im Paper des Professors (PDF).