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

25

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?


quelle
Delphi hat Hilfsklassen, es ist wie das Hinzufügen von Methoden zu vorhandenen Klassen, ohne diese wirklich zu ändern. In Delphi ist beispielsweise eine TBitmap-Klasse in der Grafikeinheit definiert. Sie können eine Hilfsklasse erstellen, die TBitmap beispielsweise eine Flip-Funktion hinzufügt. Solange sich die Hilfsklasse im Gültigkeitsbereich befindet, können Sie MyBitmap.Flip aufrufen.
Bill

Antworten:

14

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.

yannis
quelle
Dies ist genau das, was ich brauchte: Ich erkannte, dass die Verwendung eines Adapters mit Abhängigkeitsinjektion schrauben würde, aber die Verwendung eines Dekorateurs umgeht dies.
5

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.

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 auch die horizontale Wiederverwendung oder die Laufzeitänderung und -ergänzung des Klassen- / Objektverhaltens mit Reflektion emulieren .

dukeofgaming
quelle
4

Wenn eine Anforderung besteht, dass die bakeryInstanz ihr Verhalten dynamisch ändern muss (abhängig von den Benutzeraktionen usw.), sollten Sie sich für das Decorator-Muster entscheiden .

Wenn bakerysich das Verhalten nicht dynamisch ändert, Sie jedoch die Bakery class(externe API usw.) nicht ändern können , sollten Sie sich für das Adaptermuster entscheiden .

Wenn bakerysich das Verhalten nicht dynamisch ändert und Sie Änderungen vornehmen können Bakery class, sollten Sie die vorhandene Schnittstelle (wie ursprünglich vorgeschlagen) erweitern oder eine neue Schnittstelle einführen BrownieInterfaceund Bakeryzwei Schnittstellen implementieren lassen BakeryInterfaceund BrownieInterface.
Andernfalls fügen Sie Ihrem Code unnötige Komplexität hinzu (mithilfe des Decorator-Musters), und das ohne guten Grund!

Dime
quelle