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?
Odpowiedzi:
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.
źródło
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 .:
Na koniec można również emulować ponowne użycie w poziomie lub „modyfikację” i „dodanie” zachowania klasy / obiektu za pomocą odbicia .
źródło
Jeśli istnieje wymóg, że
bakery
instancja musi dynamicznie zmieniać swoje zachowanie (w zależności od działań użytkownika itp.), Należy wybrać wzorzec Dekoratora .Jeśli
bakery
nie zmienia dynamicznie swojego zachowania, ale nie możesz modyfikowaćBakery class
(zewnętrznego API itp.), Powinieneś wybrać wzorzec adaptera .Jeśli
bakery
nie zmienia dynamicznie swojego zachowania i można go zmodyfikowaćBakery class
, należy rozszerzyć istniejący interfejs (jak pierwotnie zaproponowano) lub wprowadzić nowy interfejsBrownieInterface
i pozwolić naBakery
wdrożenie dwóch interfejsówBakeryInterface
iBrownieInterface
.W przeciwnym razie dodasz niepotrzebną złożoność do kodu (używając wzorca Dekorator) bez powodu!
źródło
Wystąpił problem z wyrażeniem. W tym artykule Stuart Sierra omawia rozwiązanie clojures, ale podaje również przykład w Javie i wymienia popularne rozwiązania w klasycznym OO.
Jest też prezentacja Chrisa Housera, która omawia prawie ten sam grunt.
źródło