Ich habe eine Schnittstelle, die eine bestimmte Menge an gut definierten Funktionen hat. Sagen wir:
interface BakeryInterface {
public function createCookies();
public function createIceCream();
}
Dies funktioniert gut für die meisten Implementierungen der Schnittstelle, aber in einigen Fällen muss ich einige neue Funktionen hinzufügen (wie vielleicht in eine neue Methode gerollt, createBrownies()
). Der naheliegende / naive Ansatz wäre, die Schnittstelle zu erweitern:
interface BrownieBakeryInterface extends BakeryInterface {
public function createBrownies();
}
Das hat aber den großen Nachteil, dass ich die neue Funktionalität nicht hinzufügen kann, ohne die vorhandene API zu ändern (wie das Ändern der Klasse, um die neue Schnittstelle zu verwenden).
Ich dachte darüber nach, einen Adapter zu verwenden, um die Funktionalität nach der Instanziierung hinzuzufügen:
class BrownieAdapter {
private brownieBakery;
public function construct(BakeryInterface bakery) {
this->brownieBakery = bakery;
}
public function createBrownies() {
/* ... */
}
}
Was würde mich soetwas net:
bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();
Dies scheint eine gute Lösung für das Problem zu sein, aber ich frage mich, ob ich damit die alten Götter erwecke. Ist der Adapter der richtige Weg? Gibt es ein besseres Muster, dem man folgen kann? Oder sollte ich wirklich nur die Kugel beißen und nur die ursprüngliche Schnittstelle erweitern?
Antworten:
Jedes der Handle-Body-Muster kann in Abhängigkeit von Ihren genauen Anforderungen, Ihrer Sprache und der gewünschten Abstraktionsstufe zur Beschreibung passen.
Der puristische Ansatz wäre das Decorator-Muster , das genau das tut, wonach Sie suchen, und Objekte dynamisch mit Verantwortlichkeiten versieht. Wenn Sie tatsächlich Bäckereien bauen, ist es definitiv übertrieben und Sie sollten einfach mit Adapter gehen.
quelle
Untersuchen Sie das Konzept der horizontalen Wiederverwendung , in dem Sie Dinge wie Traits , die noch experimentelle, aber bereits produktionssichere aspektorientierte Programmierung und die manchmal verhassten Mixins finden .
Eine direkte Methode zum Hinzufügen von Methoden zu einer Klasse hängt auch von der Programmiersprache ab. Ruby ermöglicht das Patchen von Affen, während die Vererbung auf der Grundlage von Javascript- Prototypen , bei der Klassen nicht wirklich existieren, erstellt und kopiert wird.
Schließlich können Sie auch die horizontale Wiederverwendung oder die Laufzeitänderung und -ergänzung des Klassen- / Objektverhaltens mit Reflektion emulieren .
quelle
Wenn eine Anforderung besteht, dass die
bakery
Instanz ihr Verhalten dynamisch ändern muss (abhängig von den Benutzeraktionen usw.), sollten Sie sich für das Decorator-Muster entscheiden .Wenn
bakery
sich das Verhalten nicht dynamisch ändert, Sie jedoch dieBakery class
(externe API usw.) nicht ändern können , sollten Sie sich für das Adaptermuster entscheiden .Wenn
bakery
sich das Verhalten nicht dynamisch ändert und Sie Änderungen vornehmen könnenBakery class
, sollten Sie die vorhandene Schnittstelle (wie ursprünglich vorgeschlagen) erweitern oder eine neue Schnittstelle einführenBrownieInterface
undBakery
zwei Schnittstellen implementieren lassenBakeryInterface
undBrownieInterface
.Andernfalls fügen Sie Ihrem Code unnötige Komplexität hinzu (mithilfe des Decorator-Musters), und das ohne guten Grund!
quelle
Sie haben das Ausdrucksproblem festgestellt. In diesem Artikel von Stuart Sierra wird die Lösung von Clojures erörtert, er gibt jedoch auch ein Beispiel in Java und listet die gängigen Lösungen in klassischem OO auf.
Es gibt auch diese Präsentation von Chris Houser , die fast den gleichen Grund bespricht.
quelle