35
Tell, Don‘t Ask! Erstellung aussagekräftiger Schnittstellen (c) Carsten Hetzel, 2014

Tell, Don't Ask - Aussagekräftige Schnittstellen

Embed Size (px)

Citation preview

Page 1: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Tell, Don‘t Ask!Erstellung aussagekräftiger Schnittstellen(c

) C

ars

ten

He

tze

l, 2

014

Page 2: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Tell, Don't Ask - Erstellung aussagekräftiger Schnittstellen

Es gibt eine Vielzahl von Programmierprinzipien im Bereich objektorientiert Softwareentwicklung aber keines hilft wie "Tell, Don't Ask!" dabei, aussagekräftige Schnittstellen für Klassen zu erstellen.

"Tell, Don't Ask!" (TDA) besagt, dass wir statt Objekte nach ihrem Zustand zu fragen und diesen anschließend auszuwerten, die Klasse selber aussagekräftige Methoden anbieten lassen sollen, die die Auswertung oder Verarbeitung übernehmen.

(c) Carsten Hetzel, 20142

Page 3: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Aufgabe: Welche Programmierprinzipien kennen Sie noch?

(c)

Ca

rste

n H

etz

el,

20

14

Page 4: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Programmierprinzipien

- Information Hiding- Least Knowledge Principle- Lose Kopplung, starke Bindung (Kohärenz)- Die SOLID-Prinzipien- Programmiere gegen Interfaces, nicht gegen Implementierungen- Verwende Abhängigkeiten zu Abstraktionen, nicht zu konkreten Klassen- Don't Repeat Yourself (DRY)- Kapsle variable Bestandteile (Strategie-Pattern)- Bevorzuge Komposition statt Vererbung- Das "Hollywood Prinzip" (Don't call us, we call you!)- Strebe lose gekoppelte Systeme an- Keep It Simple and Stupid (KISS)

(c) Carsten Hetzel, 20144

Page 5: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein einfaches Beispiel

(c)

Ca

rste

n H

etz

el,

20

14

Page 6: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein einfaches Beispiel

Eine der häufigsten Anwendungen dieses Prinzips ist die Kapselung von Statusabfragen.Welche Nachteile hat z.B. folgender Code:

if ($myClass->getStatus() == 10) {

// Do something ...

} else {

// Do something else

}

(c) Carsten Hetzel, 20146

Page 7: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein einfaches Beispiel

if ($myClass->getStatus() == 10) {

// Do something ...

} else {

// Do something else

}

Die Bedeutung des Werts 10 ist nicht klar - der Code ist also undurchsichtig (Opazität/Opacity hoch)

Wenn wir diese Abfrage häufiger benötigen, verteilt sie sich über den gesamten Client Code

Sollte sich Abfrage ändern (z.B. ein weiterer Status zu beachten sein), müssen wir viele Stellen anpassen

(c) Carsten Hetzel, 20147

Page 8: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein einfaches Beispiel

Was ist mit folgender Änderung?

if ($myClass->getStatus() == MyClass::PREMIUM_USER) {

// Do something ...

} else {

// Do something else

}

(c) Carsten Hetzel, 20148

Page 9: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein einfaches Beispiel

if ($myClass->getStatus() == MyClass::PREMIUM_USER) {

// Do something ...

} else {

// Do something else

}

Jetzt verstehen wir zwar die Bedeutung der Abfrage besser, aber die anderen Probleme bestehen weiterhin.

Schlimmer noch: Wir haben die Klasse um Elemente (Konstanten) erweitert, die dazu verleiten wenn nicht sogargeradezu rechtfertigen weitere Bedingungen im Client Code zu fromulieren.

(c) Carsten Hetzel, 20149

Page 10: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein einfaches Beispiel

Wenn wir die Abfrage in eine Methode verlagern (siehe Abfragemethode/QueryMethod), dann wird derCode kompakt und aussagekräftig:

if ($myClass->isPremiumUser()) {

// Do something ...

} else {

// Do something else

}

Sogar eine Veränderung der Bedingung kann nun ohne Anpassung des Client Codes vorgenommen werden.

(c) Carsten Hetzel, 201410

Page 11: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein komplexeres Beispiel

(c)

Ca

rste

n H

etz

el,

20

14

Page 12: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein komplexeres Beispiel

Stellen Sie sich folgende Situation vor: Sie verwenden ein Framework, welches Ihnen dieVerarbeitung von Formularen erlaubt. Diese Klasse sieht etwa wie folgt aus:

class Form{

// ...public function getValue($key){

// ...return $value;

}

public function addFormElement($key, FormElement$element)

{// ...

}}

(c) Carsten Hetzel, 201412

Page 13: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein komplexeres Beispiel

Einer Form können Sie also verschiedene Elemente wie z.B. ein Eingabefeld oder eine Checkbox hinzufügen.

Um ihre Dialoge wieder verwenden zu können entscheiden Sie sich konkrete Ableitungen dieser Klasse zu erstellen, z.B. ein Kontaktformular, um von neuen Kunden Kontaktdaten aufzunehmen:

(c) Carsten Hetzel, 201413

Page 14: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein komplexeres Beispiel

class ContactForm extends Form{

const KEY_NAME = 'name';const KEY_AGE = 'age';const KEY_EMAIL = 'email';

// ...

public function __construct(){

$nameInput = $this->createNameInputField();$this->addFormElement(self::KEY_NAME, $nameInput);

$ageInput = $this->createAgeInputField();$this->addFormElement(self::KEY_AGE, $ageInput);

// ...}

}

Um Fehler beim Zugriff auf die einzelnen Form-Felder zu vermeiden bietet die KlasseKonstanten an, welche dann beim Auslesen verwendet werden können.

(c) Carsten Hetzel, 201414

Page 15: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein komplexeres Beispiel

Der Controller sieht etwa folgendermaßen aus:

class UglyContactController{

// ...

public function createNewContact(){

// ...$form = new ContactForm();// ...if ($form->isValid()) {

$contact = new Contact();$contact->setName($form->getValue(ContactForm::KEY_NAME));$contact->setAge($form->getValue(ContactForm::KEY_AGE));$contact->setEMail($form->getValue(ContactForm::KEY_AGE));$contact->save();

} else {// ...

}// ...

}}

Was halten Sie von dieser Lösung? Welche Probleme sehen Sie?

(c) Carsten Hetzel, 201415

Page 16: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Ein komplexeres Beispiel

Schlecht lesbar

Nicht wiederverwendbar

Fehleranfällig

...

(c) Carsten Hetzel, 201416

Page 17: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Konkretisierungen können auch die API erweitern

(c)

Ca

rste

n H

etz

el,

20

14

Page 18: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Konkretisierungen können auch die API erweitern

Da wir sowieso eine separate ContactForm-Klasse haben, können wir uns die Verwendung der Konstanten sparen, indem wir direkt entsprechende Getter anbieten:

class BetterContactController{

public function createNewContact(){

// ...$form = new ContactForm();// ...if ($form->isValid()) {

$contact = new Contact();$contact->setName($form->getName());$contact->setAge($form->getAge());$contact->setEMail($form->getEMail());$contact->save();

} else {// ...

}// ...

}}

(c) Carsten Hetzel, 201418

Page 19: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Konkretisierungen können auch die API erweitern

Diese Version des Controllers ist schon deutlich einfacher zu lesen und Fehler werden schneller entdeckt. Aber wenn wir die Klasse Contact erweitern - z.B. um eineTelefonnummer - dann müssen wir alle Controller nach solchen Code-Stellen durchsuchen und sie anpassen.

Verwenden wir die Klasse ContactForm, dann müssen wir auch den Code-Block mit den Setter-Aufrufen kopieren und haben einen klassischen Fall von Code-Duplizierung.

(c) Carsten Hetzel, 201419

Page 20: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vermitteln der Intention

(c)

Ca

rste

n H

etz

el,

20

14

Page 21: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vermitteln der Intention

Was soll denn an der Stelle passieren, wenn die Eingabedaten des Formulars korrekt waren und gespeichert werden können?

Im Bereich mit den Setter-Aufrufen sollen die Daten aus dem Formular in die Contact-Instanz übertragen werden. Eine Methode wie "transferToContact“ oder "fillContact()" verdeutlichen die Intention besser:

(c) Carsten Hetzel, 201421

Page 22: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vermitteln der Intention

class BetterContactController{

// ...

public function createNewContact(){

// ...$form = new ContactForm();// ...if ($form->isValid()) {

$contact = new Contact();$form->fillContact($contact);$contact->save();

} else {// ...

}// ...

}}

(c) Carsten Hetzel, 201422

Page 23: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vermitteln der Intention

Inzwischen sieht der Code leserlich aus. Aber will man wirklich explizit ausdrücken, dass die Daten aus dem Formular in den Kontakt übertragen werden?

Eigentlich würden wir doch erwarten, dass das automatisch passiert, oder?

(c) Carsten Hetzel, 201423

Page 24: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vermitteln der Intention

class ContactController{

// ...

public function createNewContact(){

// ...$contact = new Contact();$form = new ContactForm($contact);// ...if ($form->isValid()) {

$contact->save();} else {

// ...}// ...

}}

(c) Carsten Hetzel, 201424

Page 25: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vermitteln der Intention

Im Grunde hat sich nur folgendes geändert: Die Methode "fillContact()" muss nicht mehr explizit aufgerufen werden, sondern gehört quasi zum Lebenszyklus des Formulars. DerClient-Code ist schlanker geworden und entspricht eher unseren Erwartungen.

Darüber hinaus haben wir einen weiteren Vorteil erreicht: Die Validierung der Eingabedaten kann nun von Contactdurchgeführt werden. Es ist viel sinnvoller die Validierung derAttribute eines Modells dem Modell zu überlassen, schließlich können Eingabedaten von allen möglichen Stellen des Systems und unterschiedlichsten Schnittstellen her kommen.

(c) Carsten Hetzel, 201425

Page 26: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

(c)

Ca

rste

n H

etz

el,

20

14

Page 27: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

Angenommen Sie sollen ein System von Behältern beschreiben, welche Flüssigkeiten aufnehmen können (z.B. Flaschen).

In diesem System kann es beliebig viele Behälter geben, die Menge an Wasser soll aber immer gleich bleiben.

Wasser kann immer nur zwischen zwei Behälternausgetauscht werden.

Wie würden Sie dieses Problem lösen?

(c) Carsten Hetzel, 201427

Page 28: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

class Bottle

{

private $liters;

// ...

}

class AppController

{

public function transfuseAction($amount, $sourceBottleId,

$targetBottleId)

{

// ..

$source = $this->getBottleById($sourceBottleId);

$target = $this->getBottleById($targetBottleId);

$source->setLiters($source->getLiters() - $amount);

$target->setLiters($target->getLiters() + $amount);

}

}

(c) Carsten Hetzel, 201428

Page 29: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

Welche Probleme hat diese Lösung?

Was wollen wir eigentlich machen?

Finden wir eine sprechendere Lösung, die auch direkt die Rahmenbedingungen des Systems einhält:

(c) Carsten Hetzel, 201429

Page 30: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

class Transfusion

{

private $liters;

public function __construct($liters, Bottle $source, Bottle

$target)

{

$source->reduceBy($this);

$target->fillBy($this);

}

public function getLiters()

{

return $this->liters;

}

}

(c) Carsten Hetzel, 201430

Page 31: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

Entsprechend sieht die Bottle-Klasse folgendermaßen aus:

class Bottle

{

// ...

public function reduceBy(Transfusion $t)

{

$this->liters -= $t->getLiters();

}

// ...

}

(c) Carsten Hetzel, 201431

Page 32: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Von verstreuter Business-Logikzu Value-Objects

Und abschließend der Controller:

class AppController

{

public function transfuseAction($amount, $sourceBottleId,

$targetBottleId)

{

// ..

$source = $this->getBottleById($sourceBottleId);

$target = $this->getBottleById($targetBottleId);

$transfusion = new Transfusion($amount, $source,

$target);

}

}

Welche Vorteile hat diese Lösung?

(c) Carsten Hetzel, 201432

Page 33: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Aufgabe: Markierung von "besonderen" Rechnungsposten

(c)

Ca

rste

n H

etz

el,

20

14

Page 34: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Aufgabe: Markierung von "besonderen" Rechnungsposten

Auf dem Bildschirm sollen alle Posten einer Rechnung aufgelistet und diejenigen Posten mit einem "X" markiert werden, die einen Wert von über 100€ haben.

(c) Carsten Hetzel, 201434

Page 35: Tell, Don't Ask  - Aussagekräftige Schnittstellen

Vielen Dank für Ihre Aufmerksamkeit!

(c) Carsten Hetzel, 201435