Czytałem artykuł programistyczny, w którym wspomniałem o wzorze dekoratora. Przez jakiś czas programowałem, ale nie miałem formalnego wykształcenia ani szkolenia, ale staram się poznać standardowe wzorce i tym podobne.
Poszukałem Dekoratora i znalazłem na nim artykuł w Wikipedii . Teraz rozumiem koncepcję wzoru Dekoratora, ale ten fragment był mnie trochę zdezorientowany:
Jako przykład rozważmy okno w systemie okienkowym. Aby umożliwić przewijanie zawartości okna, możemy dodać do niego odpowiednio poziome lub pionowe paski przewijania. Załóżmy, że okna są reprezentowane przez instancje klasy Window, i załóżmy, że ta klasa nie ma funkcji dodawania pasków przewijania. Możemy stworzyć podklasę ScrollingWindow, która je udostępnia, lub możemy stworzyć ScrollingWindowDecorator, który doda tę funkcjonalność do istniejących obiektów Windows. W tym momencie każde rozwiązanie byłoby w porządku.
Załóżmy teraz, że chcemy również możliwości dodawania ramek do naszych okien. Ponownie, nasza oryginalna klasa Window nie ma wsparcia. Podklasa ScrollingWindow stanowi teraz problem, ponieważ skutecznie stworzyła nowy rodzaj okna. Jeśli chcemy dodać obsługę granic do wszystkich okien, musimy utworzyć podklasy WindowWithBorder i ScrollingWindowWithBorder. Oczywiście problem ten nasila się z każdą dodaną nową funkcją. W przypadku rozwiązania dekoratora po prostu tworzymy nowy BorderedWindowDecorator - w czasie wykonywania możemy dekorować istniejące okna za pomocą ScrollingWindowDecorator lub BorderedWindowDecorator lub obu, w zależności od potrzeb.
OK, kiedy mówią, aby dodać obramowanie do wszystkich okien, dlaczego po prostu nie dodać funkcji do oryginalnej klasy Window, aby umożliwić tę opcję? Moim zdaniem podklasowanie służy jedynie do dodawania określonych funkcji do klasy lub zastępowania metody klasowej. Gdybym musiał dodać funkcjonalność do wszystkich istniejących obiektów, dlaczego po prostu nie zmodyfikowałbym nadklasy, aby to zrobić?
W artykule była inna linia:
Wzór dekoratora jest alternatywą dla podklas. Podklasowanie dodaje zachowanie podczas kompilacji, a zmiana wpływa na wszystkie instancje oryginalnej klasy; dekorowanie może zapewnić nowe zachowanie w czasie wykonywania dla poszczególnych obiektów.
Nie rozumiem, co mówią „... zmiana wpływa na wszystkie wystąpienia oryginalnej klasy” - jak podklasa zmienia klasę nadrzędną? Czy nie o to chodzi w podklasie?
Zakładam, że artykuł, podobnie jak wiele Wiki, po prostu nie jest napisany jasno. Widzę użyteczność Dekoratora w ostatnim wierszu - „... zapewniają nowe zachowanie w czasie wykonywania dla poszczególnych obiektów”.
Bez zapoznania się z tym wzorcem, gdybym musiał zmienić zachowanie w czasie wykonywania dla poszczególnych obiektów, prawdopodobnie wbudowałbym pewne metody w nadklasę lub podklasę, aby włączyć / wyłączyć to zachowanie. Pomóż mi naprawdę zrozumieć przydatność Dekoratora i dlaczego moje myślenie dla początkujących jest wadliwe?
Odpowiedzi:
Wzorzec dekoratora jest tym, który preferuje kompozycję zamiast dziedziczenia [kolejny paradygmat OOP, o którym warto się dowiedzieć]
Główną zaletą wzoru dekoratora - ponadklasowanie jest umożliwienie większej liczby opcji mieszania i dopasowywania. Jeśli masz na przykład 10 różnych zachowań, które może mieć okno, oznacza to - z podklasą - musisz utworzyć każdą inną kombinację, która nieuchronnie obejmie wiele ponownego użycia kodu.
Co się jednak stanie, gdy zdecydujesz się dodać nowe zachowanie?
Dzięki dekoratorowi po prostu dodajesz nową klasę, która opisuje to zachowanie i to wszystko - wzorzec pozwala na efektywne upuszczenie tego bez modyfikowania reszty kodu.
Dzięki podklasom masz koszmar na rękach.
Pytanie, które zadałeś, brzmiało: „w jaki sposób podklasa zmienia klasę nadrzędną?” To nie tak, że zmienia klasę nadrzędną; kiedy mówi instancję, oznacza to dowolny obiekt, który „utworzyłeś” (jeśli używasz Java lub C #, na przykład za pomocą
new
polecenia). Odnosi się to do tego, że kiedy dodajesz te zmiany do klasy, nie masz wyboru, aby ta zmiana była dostępna, nawet jeśli tak naprawdę nie jest to potrzebne.Alternatywnie, możesz umieścić całą jego funkcjonalność w jednej klasie z włączoną / wyłączoną za pomocą flag ... ale kończy się to jedną klasą, która staje się coraz większa wraz z rozwojem projektu.
Nie jest niczym niezwykłym, aby rozpocząć projekt w ten sposób i przekształcić wzór dekoratora, gdy osiągniesz skuteczną masę krytyczną.
Interesujący punkt, który należy zrobić: możesz dodać tę samą funkcjonalność wiele razy; więc możesz na przykład mieć okno z podwójną, potrójną lub dowolną ilością lub obramowaniami według potrzeb.
Głównym punktem tego wzorca jest umożliwienie zmian w czasie wykonywania: możesz nie wiedzieć, jak chcesz wyglądać okna, dopóki program nie uruchomi się, a to pozwala na łatwą modyfikację. To prawda, że można tego dokonać poprzez podklasę, ale nie tak ładnie.
I wreszcie, umożliwia dodanie funkcji do klas, których edytowanie może nie być możliwe - na przykład w klasach zamkniętych / końcowych lub w innych interfejsach API
źródło
Rozważ możliwości dodawania pasków przewijania i ramek z podklasami. Jeśli chcesz każdej możliwości, otrzymujesz cztery klasy (Python):
Teraz
WindowWithScrollBarAndBorder.draw()
okno zostanie narysowane dwukrotnie, a po raz drugi może zastąpić już narysowany pasek przewijania, zależnie od implementacji. Twój kod pochodny jest ściśle powiązany z implementacją innej klasy i musisz się tym przejmować za każdym razem, gdy zmieniaszWindow
zachowanie klasy. Rozwiązaniem byłoby skopiowanie i wklejenie kodu z superklas do klas pochodnych i dostosowanie go do potrzeb klas pochodnych, ale każda zmiana w superklasie musi zostać ponownie wklejona i ponownie dostosowana, tak aby ponownie klasy pochodne były ściśle powiązane z klasą bazową (poprzez potrzebę dostosowania i skopiowania-wklejenia). Innym problemem jest to, że jeśli potrzebujesz jeszcze jednej właściwości, którą okno może mieć lub nie, musisz podwoić każdą klasę:Oznacza to dla zestawu wzajemnie niezależnych cech f z | f | ponieważ jest liczbą funkcji, musisz zdefiniować 2 ** | f | zajęcia, np. jeśli masz 10 funkcji, otrzymasz 1024 ściśle powiązane klasy. Jeśli użyjesz Wzorca Dekoratora, każda funkcja ma swoją własną niezależną i luźno powiązaną klasę, a masz tylko 1 + | f | klasy (to 11 w powyższym przykładzie).
źródło
Nie jestem ekspertem od tego konkretnego wzorca, ale widzę, że wzorzec Dekoratora można zastosować do klas, które mogą nie mieć możliwości modyfikacji lub podklasy (na przykład mogą nie być twoim kodem i być zapieczętowane ). W twoim przykładzie, co jeśli nie napisałeś klasy Window, a po prostu ją konsumujesz? Tak długo, jak klasa Window ma interfejs i programujesz przeciwko temu interfejsowi, twój Dekorator może używać tego samego interfejsu, ale rozszerzyć jego funkcjonalność.
Przykład, o którym wspominasz, jest tutaj właściwie schludny:
http://www.oodesign.com/decorator-pattern.html
źródło