Technologia Programowania 2016/2017 Wykład 5 Wzorce GoF Jakub Lemiesz
Wzorce GoF Kreacyjne Builder Singleton Simple Factory Factory Method Abstract Factory Prototype Strukturalne Adapter Decorator Proxy Composite Facade Flyweight Bridge Czynnościowe Strategy Template Method State Command Observer Interpreter Iterator Mediator Visitor Memento 2 / 36
GoF: Template Method 1 Jak zmieniać wybrane kroki ustalonego algorytmu w zgodzie z open-close principle? 2 Zdefiniuj w nadklasie metodę szablonową będącą niezmiennym szkieletem algorytmu (nie może być przesłonięta w podklasach) 3 W metodzie szablonowej wywołuj metody-haczyki (ang. hook method), reprezentujące zmienne kroki algorytmu, które mogą być przesłonięte w podklasach 4 Szablonowa public, haczyki protected 3 / 36
Template Method tak działają frameworki 4 / 36
Template Method tak działają frameworki 4 / 36
Template Method przykłady 1 JUnit 3: możemy przesłonić setup, teststh, teardown ale nie możemy zmienić metody run, w której są wywoływane w ustalonej kolejności 2 Thread thread = new Thread() { @Override public void run() {...} }; thread.start(); 3 GoF:Factory Method to szczególny przypadek Template Method 5 / 36
Po co nam fabryki? Problem: kto ma odpowiadać za tworzenie obiektów 1 o złożonej logice wytwórczej? 2 odpowiedniego typu? 3 nie związanych z warstwą dziedzinową? Pomysł: Utwórzmy obiekt fabryki (Pure Abstraction) i przydziel mu te zobowiązania (High Cohesion). Umożliwi to ochronę przed zmianami (PV). 6 / 36
Simple Factory http://www.yuml.me/ krok 1 Klient wywołuje metodę tworzącą fabryki (najczęściej fabryka to singleton) krok 2 Wszystkie tworzone produkty implementują ten sam interfejs. Fabryka decyduje jaki produkt tworzy (decyzja np. w oparciu o przekazany parametr, refleksje). krok 3 Fabryka zwraca stworzony obiekt do klienta. Klient nie musi wiedzieć jaki obiekt dostanie, ale zna interfejs. 7 / 36
Simple Factory parametr (jaka zasada złamana?) W kliencie SimpleFactory.getInstance().createProduct( A ); public class SimpleFactory {... public IProduct createproduct(string type) { IProduct prod = null; if (type.equals("a")) prod = new ProductA(); else prod = new ProductB(); return prod; 8 / 36
Simple Factory refleksja W kliencie SimpleFactory.getInstance().createProduct(); public class SimpleFactory {... public IProduct createproduct() { String classname = System.getProperty(..) ; IProduct prod = (IProduct)Class.forName(className).newInstance(); return prod; 9 / 36
GoF: Factory Method Factory Method czyli jak to zrobić bez refleksji? Trudno przewidzieć jakie produkty będziemy tworzyć w przyszłości. Zostawiamy metodę szablonową (createproduct) w nadklasie, a specyfikację metody tworzącej (create) zdefiniujmy w podklasach. public abstract class Factory { public IProduct createproduct() { IProduct prod = create(); prod.additionalprocessing(); return prod; } protected abstract IProduct create(); W kliencie Factory f = new ConcreteFactory(); IProduct p = f.createproduct() 10 / 36
GoF: Factory Method krok 1 Klient ma referencje do abstrakcyjnej fabryki Factory i inicjalizuje ją jedną z jej podklas. krok 2 Factory ma chronioną abstrakcyjną metodę create() z której korzysta publiczna metoda createproduct(). Metoda create() jest implmentowana w fabrykach konkretnych. krok 3 Fabryka konkretna tworzy produkt implementujący interfejs IProduct i zwraca go do klienta przez metodę createproduct(). Klient zna jedynie interfejs produktu. 11 / 36
GoF: Abstract Factory Abstract Factory vs. Factory Method 1 W Factory Method mamy jeden rodzaj produktów, w Abstract Factory rodzinę powiązanych produktów (wiele interfejsów) 2 Konkretne fabryki dziedziczące po Abstract Factory mogą wykorzystywać metodę szablonową, ale nie muszą Factory Method Factory f = new ConcreteFactory(); IProduct p = f.createproduct() Abstract Factory Factory f = new ConcreteFactory(); IProductA pa = f.createproducta(); IProductB pb = f.createb(); //metoda nieszablonowa 12 / 36
GoF: Abstract Factory 13 / 36
AbstractFactory przykład (Larman) 1 Piszemy system do obsługi kas (Point Of Sale) 2 Kasy mają różnych producentów, każda kasa wymaga odpowiedniej rodziny sterowników (np. do szuflady na gotówkę, monet, klawiatury) 3 Rodzinę sterowników dostarcza producent kasy 4 Jak napisać system możliwie uniwersalnie, by móc łatwo zmieniać kasy bez zmian w systemie? 14 / 36
Abstract Factory przykład (Larman) 15 / 36
AbstractFactory od strony klienta Nie ładnie: IPOSDeviceFactory f = new IBMPOSDeviceFactory(); ładniej: IPOSDeviceFactory f = POSDeviceFactory.getInstance(); ICashDrawer c = f.getnewcashdrawer(); ICoinDispenser c = f.getnewcoindispenser(); 16 / 36
Wzorce kreacyjne podsumowanie Oddzielenie reguł wyboru i tworzenia obiektów od miejsc ich użycia, przeniesienie reguł tworzenia do specjalnej klasy (High Cohesion) Przygotowanie na potencjalne zmiany (np. pojawienie się nowych produktów) 17 / 36
Abstract Factory vs. Builder różnice AF Produkty mają wspólny interfejs. BU Produkty tworzone przez różnych budowniczych mogą być różne, więc nie ma powodu by ustalać dla nich wspólny interfejs. AF Akcent jest na tworzenie rodziny powiązanych produktów, każdy produkt w jednym kroku. BU Akcent jest na tworzenie złożonego produktu krok po kroku. Kroki możemy omijać/modyfikować co daje większa elastyczność. AF Klient używa metod Abstract Factory do stworzenia grupy produktów, ale często nie wie z jakiej konkretnej fabryki korzysta. BU Klient wybiera budowniczego, od którego zależy postać produktu i (poprzez nadzorcę) poleca ten produkt zbudować. 18 / 36
GoF: Facade Problem: sprzężenie między komponentami systemu Rozwiązanie: klasa Facade dostarczająca dla całego podsystemu wysokopoziomowy interfejs źródło 19 / 36
GoF: Facade typowe zastosowania 1 Prosty interfejs dla złożonego podsystemu (łatwiej zrozumieć i wykorzystać funkcjonalność) 2 Zebranie kilku interfejsów w jeden sensowny (np. interfejsy mogą być źle zaprojektowane) 3 Punkt wejściowy do podsystemu/warstwy niezależny rozwój podsystemów blokada bezpośrednegoi dostępu do metod (np. względy bezpieczeństwa) 20 / 36
GoF: Facade zadanie na lab 1 Mamy dostęp do interfejsów różnych systemów (na razie mocki) 2 Chcemy napisac aplikacje, która odwołując się do tych systemów umożliwi kupienie książki 21 / 36
GoF: Facade zadanie na lab 22 / 36
GoF: Strategy 1 W klasie Sale w metodzie gettotal() mamy 10 metod naliczania upustów i 10 podatków 2 Template Method? Ile klas trzeba by stworzyć? (dla uproszczenia załóżmy, że zniżek nie można łączyć i płaci się tylko jeden podatek) 3 Lepsze rozwiązanie: każdy algorytm w osobnej klasie, klasy powinny mieć wspólny interfejs. Wybieraj algorytmy przez wymienne obiekty. 23 / 36
Strategy podstawowy schemat 24 / 36
Strategy przykład 25 / 36
Strategy przykład 25 / 36
Strategy uwagi 1 Komunikat do obiektu kontekstowego i do obiektu strategii typowo ma tę samą nazwę: gettotal(), gettotal(sale) 2 gettotal(this) - o. kontekstowy przekazuje referencje do siebie na potrzeby współpracy, ogólnie: unikamy ekstrakcji 3 Korzyści: wspiera open-close principle ale mamy mniej klas niż w Template Method (ile?) 26 / 36
GoF: Composite A co jeśli upusty w Sale można łączyć? Jak zdefiniować strukturę by liście (np. upusty) i węzły (np. upusty łączone) traktować jednolicie? 27 / 36
GoF: Composite 28 / 36
Composite przykład z upustami 29 / 36
Composite zadanie na lab Program do animacji 2D: Rectangle, Circle, Traingle mogą zawierać dowolną liczbę figur, Line nie może niczego zawierać. Figury mają współrzędne (x,y) i mamy metodę move... 30 / 36
GoF: State Problem: Obiekt może znajdować się w różnych stanach Zachowanie obiektu zależy od jego stanu (np. metody zawiera logikę warunkową case) Rozwiązanie: Utwórz klasy reprezentujące poszczególne stany (implementujące wspólny interfejs) Deleguj operacje do obiektu określającego aktualny stan (tak jak w GoF:Strategy) Określ przejścia między stanami 31 / 36
GoF: State 32 / 36
Przykład: stany transakcyjne 1 Stan transakcyjny określa zgodność obiektu z tym co jest w bazie danych: New, OldClean, OldDirty,... 2 Operacje commit, rollback, save i delete zmieniają stan transakcyjny, rezultat zależy od aktualnego stanu 3 Można by to spróbować zapisać w kodzie instrukcjami warunkowymi, ale nie byłoby to ładne rozwiązanie... 33 / 36
Przykład: stany transakcyjne 34 / 36
Przykład: stany transakcyjne 35 / 36
Wydajność stany transakcyjne jako singletony 36 / 36