it-swarm.com.de

Wie soll ich einem bereits vorhandenen Objekt Funktionen hinzufügen?

Ich habe eine Schnittstelle, die eine bestimmte Menge an genau 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 (z. B. in eine neue Methode, createBrownies()). Der offensichtliche/naive Ansatz dazu wäre, die Schnittstelle zu erweitern:

interface BrownieBakeryInterface extends BakeryInterface {
  public function createBrownies();
}

Aber es hat einen ziemlich 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 habe darüber nachgedacht, 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() {
    /* ... */
  }
}

Welches würde mich so etwas wie:

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? Oder sollte ich wirklich nur in die Kugel beißen und nur die ursprüngliche Benutzeroberfläche erweitern?

25
user8

Jedes der Handle Body-Muster kann zur Beschreibung passen, abhängig von Ihren genauen Anforderungen, Ihrer Sprache und dem erforderlichen Abstraktionsgrad.

Der puristische Ansatz wäre das Decorator-Muster , das genau das tut, wonach Sie suchen, und Objekten dynamisch Verantwortlichkeiten hinzufügt. Wenn Sie tatsächlich Bäckereien bauen, ist es definitiv übertrieben und Sie sollten einfach mit Adapter gehen.

14
yannis

Untersuchen Sie das Konzept von horizontale Wiederverwendung, wo Sie Dinge wie Eigenschaften finden können, die immer noch experimentell, aber bereits produktionssicher sind - Aspektorientierte Programmierung und das manchmal gehasste Mixins .

Eine direkte Möglichkeit, einer Klasse Methoden hinzuzufügen, hängt auch von der Programmiersprache ab. Ruby erlaubt Affen-Patching während Javascript prototypbasiert Vererbung, wo Klassen nicht wirklich existieren, erstellen Sie ein Objekt und kopieren es einfach und fügen es weiter hinzu, z.

var MyClass = {
    do : function(){...}
};

var MyNewClass = new MyClass;
MyClass.undo = function(){...};


var my_new_object = new MyNewClass;
my_new_object.do();
my_new_object.undo();

Schließlich können Sie mit Reflection auch die horizontale Wiederverwendung oder die Laufzeit "Modifikation" und "Addition" des Klassen-/Objektverhaltens emulieren.

5
dukeofgaming

Wenn die Instanz bakery ihr Verhalten dynamisch ändern muss (abhängig von den Benutzeraktionen usw.), sollten Sie das Decorator-Muster wählen.

Wenn bakery sein Verhalten nicht dynamisch ändert, Sie aber Bakery class (Externe API usw.) nicht ändern können, sollten Sie sich für Adaptermuster entscheiden.

Wenn bakery sein Verhalten nicht dynamisch ändert und Sie Bakery class Ändern können, sollten Sie die vorhandene Schnittstelle erweitern (wie ursprünglich vorgeschlagen) oder ein neue SchnittstelleBrownieInterface und lassen Sie Bakery zwei Schnittstellen BakeryInterface und BrownieInterface implementieren.
Andernfalls werden Sie Ihrem Code (unter Verwendung des Decorator-Musters) ohne guten Grund unnötige Komplexität hinzufügen!

4
Dime

Sie sind auf das Ausdrucksproblem gestoßen. Dieser Artikel von Stuart Sierra beschreibt die Clojures-Lösung, gibt aber 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 so ziemlich den gleichen Grund diskutiert.

2
Pavel Penev