Jak dodać funkcjonalność do obiektu, który już istnieje?

25

Mam interfejs, który ma pewną liczbę dobrze zdefiniowanych funkcji. Powiedzmy:

interface BakeryInterface {
  public function createCookies();
  public function createIceCream();
}

Działa to dobrze w przypadku większości implementacji interfejsu, ale w kilku przypadkach muszę dodać nową funkcjonalność (np. Być może wprowadzoną do nowej metody createBrownies()). Oczywistym / naiwnym podejściem do tego byłoby rozszerzenie interfejsu:

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

Ma jednak dość duży minus, że nie mogę dodać nowej funkcjonalności bez modyfikacji istniejącego API (np. Zmiany klasy w celu użycia nowego interfejsu).

Myślałem o użyciu adaptera, aby dodać funkcjonalność po utworzeniu wystąpienia:

class BrownieAdapter {
  private brownieBakery;

  public function construct(BakeryInterface bakery) {
    this->brownieBakery = bakery;
  }

  public function createBrownies() {
    /* ... */
  }
}

Co dałoby mi coś takiego:

bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();

Wydaje się, że to dobre rozwiązanie problemu, ale zastanawiam się, czy budzę starych bogów, robiąc to. Czy adapter jest właściwą drogą? Czy jest lepszy wzór do naśladowania? A może powinienem po prostu gryźć kulę i rozszerzać oryginalny interfejs?


źródło
Delphi ma klasy pomocnicze, to jak dodawanie metod do istniejących klas bez ich modyfikacji. Na przykład Delphi ma zdefiniowaną klasę TBitmap w swojej jednostce graficznej, możesz utworzyć klasę pomocnika, która dodaje, powiedzmy, funkcję Flip do TBitmap. Tak długo, jak klasa pomocnicza jest w zasięgu, możesz wywoływać MyBitmap.Flip;
Bill

Odpowiedzi:

14

Dowolny z wzorów Korpusu uchwytu może pasować do opisu, w zależności od dokładnych wymagań, języka i potrzebnego poziomu abstrakcji.

Podejście purystyczne byłoby wzorem Dekoratora , który robi dokładnie to, czego szukasz, dynamicznie dodaje obowiązki do obiektów. Jeśli faktycznie budujesz piekarnie, to zdecydowanie przesada i powinieneś po prostu skorzystać z adaptera.

Yannis
źródło
Właśnie tego potrzebowałem: zdałem sobie sprawę z tego, że użycie adaptera wkręciłoby się przy wstrzykiwaniu zależności, ale użycie dekoratora to obejdzie.
5

Zbadanie koncepcji horyzontalnej ponownego użycia , gdzie można znaleźć rzeczy takie jak Cech , ale wciąż eksperymentalny dowód już produkcyjno- Aspect Oriented Programming i czasami znienawidzonych wstawek .

Bezpośredni sposób dodawania metod do klasy zależy również od języka programowania. Ruby pozwala na łatanie małp, podczas gdy dziedziczenie oparte na prototypach Javascript , gdzie klasy tak naprawdę nie istnieją, tworzysz obiekt, po prostu kopiujesz go i dodajesz do niego, np .:

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();

Na koniec można również emulować ponowne użycie w poziomie lub „modyfikację” i „dodanie” zachowania klasy / obiektu za pomocą odbicia .

dukeofgaming
źródło
4

Jeśli istnieje wymóg, że bakeryinstancja musi dynamicznie zmieniać swoje zachowanie (w zależności od działań użytkownika itp.), Należy wybrać wzorzec Dekoratora .

Jeśli bakerynie zmienia dynamicznie swojego zachowania, ale nie możesz modyfikować Bakery class(zewnętrznego API itp.), Powinieneś wybrać wzorzec adaptera .

Jeśli bakerynie zmienia dynamicznie swojego zachowania i można go zmodyfikować Bakery class, należy rozszerzyć istniejący interfejs (jak pierwotnie zaproponowano) lub wprowadzić nowy interfejs BrownieInterface i pozwolić na Bakerywdrożenie dwóch interfejsów BakeryInterfacei BrownieInterface.
W przeciwnym razie dodasz niepotrzebną złożoność do kodu (używając wzorca Dekorator) bez powodu!

Moneta dziesięciocentowa
źródło