Wzorce Projektowe
Plan prezentacji
1.
Czego się spodziewałem?
2.
Czym są? Do czego służą?
3.
Ogólny podział wzorców?
4.
Omówienie przykładowych wzorców:
•
Dekorator
•
Fasada
•
Pełnomocnik
•
Pyłek
•
Stan
5.
Bibliografia
Wstęp
Czego się spodziewałem?
Jakiejś magii…
Wielkich nowości…
Co spotkałem?
Znajome pomysły…
Czym są wzorce projektowe?
„Wzorzec projektowy pozwala uczyć się na sukcesach
innych zamiast nauki na własnych błędach”
(Mark Johnson)
Sprawdzone rozwiązania
efektywność
Najlepsze projekty
jakość
Dobre praktyki
skalowalność
Czym są wzorce projektowe?
„Opisem komunikujących się obiektów i klas, które są
specjalnie wykonane, aby rozwiązać ogólny problem
przy dokładnie określonym kontekście”
(GangOfFour, 1994)
„Powracającym rozwiązaniem zagadnień projektowych,
z którymi wciąż się spotykamy”
(Alpert, 1994)
„Zbiorem reguł opisujących, w jaki sposób osiągnąć
pewne cele w dziedzinie programowania”
(Pree, 1994)
Czym są wzorce projektowe?
„Każdy wzorzec opisuje problem, który ciągle na nowo
pojawia się w naszym otoczeniu i opisuje rdzeń jego
rozwiązania w taki sposób, że można go używać milion
razy i nigdy w ten sam sposób.”
(Christopher Alexander)
Elementy wzorca
Nazwa wzorca
Odzwierciedla problem, rozwiązanie i konsekwencje
danego wzorca
Problem
Opisuje zagadnienie i kontekst wystąpienia wzorca
Rozwiązanie
Opisuje elementy tworzące projekt, ich relacje,
odpowiedzialności oraz współpracę
Konsekwencje
Rezultaty zastosowania wzorca – korzyści i straty
Po co nam wzorce?
Zapobiegają ponownemu wymyślaniu kodu
zaprojektowanemu już przez kogoś innego
Zmniejszają liczbę popełnionych błędów
Zapewniają kod łatwo rozszerzalny
Ułatwiają zrozumienie kodu
Ułatwiają komunikację w zespole
Nazwa identyfikuje wzorzec
Podział wzorców
Konstrukcyjne
wykorzystuje się je do pozyskania obiektów zamiast
bezpośredniego tworzenia instancji klasy
programy zyskuj
ą
na elastyczności, gdyż można decydować, jaki
typ obiektu ma zostać utworzony w danym przypadku
Strukturalne
pomagają łączyć obiekty w większe struktury
Behawioralne
charakteryzują sposób, w jaki klasy lub obiekty oddziaływują i
dzielą odpowiedzialności
pomagają definiować przepływ danych w złożonym programie
Katalog wzorców
Przeznaczenie
Konstrukcyjne
Strukturalne
Behawioralne
Zakres
Klasa
Metoda wytwórcza
Adapter
Interpreter
Metoda szablonowa
Obiekt
Budowniczy
Fabryka abstrakcyjna
Prototyp
Singleton
Adapter
Most
Kompozyt
Dekorator
Fasada
Pełnomocnik
Pyłek
Łańcuch zobowiązań
Polecenie
Iterator
Mediator
Pamiątka
Obserwator
Stan
Strategie
Odwiedzający
Sposób omawiania
Nazwa
Przeznaczenie
Co robi? Jakich zagadnień projektowych dotyczy?
Uzasadnienie stosowania
Problem projektowy wraz z rozwiązaniem
Stosowalność
Kiedy wybrać dany wzorzec projektowy?
Struktura (uczestnicy, współpraca)
Omówienie uczestników oraz ich współpracy przy pomocy
diagramów
Konsekwencje
Co zyskujemy, a co tracimy wybierając dany wzorzec?
Przykłady zastosowania
Dekorator
Przeznaczenie
dynamicznie dołącza do obiektów dodatkowe
zobowiązania
zapewnia elastyczną alternatywę dla tworzenia
podklas w celu rozszerzania funkcjonalności
Dekorator
Uzasadnienie stosowania
Czasem chcemy dołączyć pewne zobowiązania do
pojedynczego obiektu (nie do całej klasy)
Pakiet narzędziowy do tworzenia interfejsów
użytkownika powinien np. umożliwiać dodanie do
każdego składnika takich elementów jak ramki, czy
paski przewijania
Rozwiązania:
dziedziczenie ramki, dziedziczenie przewijania (statyczne)
zagnieżdżenie obiektów – otoczka = dekorator
Dekorator
Zagnieżdżenie to może być rekurencyjne:
Dekorator
Aby to osiągnąć musimy zbudować strukturę klas:
Dekorator
Stosowalność
aby dynamicznie i w przezroczysty sposób dodawać
zobowiązania do pojedynczych obiektów
w wypadku zobowiązań które mogą być cofnięte
Gdy rozszerzanie przez definiowanie podklas jest
niepraktyczne (dużo niezależnych rozszerzeń które przy
próbie uwzględnienia każdej ich kombinacji prowadzą
do ogromnej liczby podklas)
Dekorator
Struktura
Dekorator
Uczestnicy
Komponent (KomponentWizualny)
Definiuje interfejs obiektów, do których można dynamicznie
dołączać zobowiązania
KomponentKonkretny (WidokTekstowy)
Definiuje obiekt, do którego można dołączać zobowiązania
Dekorator
Zarządza odwołaniem do obiektu Komponent i definiuje
interfejs dopasowany do interfejsu Komponentu
DekoratorKonkretny (DekoratorZRamkami,
DekoratorZSuwakiem)
Dodaje zobowiązania do komponentu
Dekorator
Współpraca
Dekorator przesyła żądania do swojego obiektu
Komponent
Może wykonywać dodatkowe operacje przed lub po
przesłaniu żądania
Dekorator
Konsekwencje
większa elastyczność niż przy stosowaniu statycznego
dziedziczenia
(dodawanie, usuwanie dekoratorów, podwójna ramka)
unikanie przeładowanych właściwościami klas na
szczycie hierarchii
„płać za to, czego potrzebujesz”, zamiast uwzględniać
wszystkie przewidywane własności przy projektowanie klasy
zasadniczej
łatwiejsza rozszerzalność (dodanie dekoratora)
Dekorator i jego komponent nie są identyczne (nie
można polegać na sprawdzaniu identyczności obiektów)
Projekt zawiera wiele małych obiektów różniących się
nie zachowaniem, a sposobem połączenia
może być trudne w zrozumieniu
Dekorator
Przykłady
Pakiety narzędziowe do tworzenie interfejsów
użytkownika
Strumienie
Strumień *strumień = new StrumieńKompresujący(
new ASCII7Strumień(
new StrumieńPlikowy(„nazwaPliku”)));
Fasada
Przeznaczenie
zapewnia jednolity interfejs dla podsystemu o wielu
interfejsach
interfejs wyższego poziomu
Fasada
Uzasadnienie stosowania
Typowym zadaniem projektowym jest sprowadzenie do
minimum zależności i komunikacji między modułami
Możemy to osiągnąć m.in. przez wprowadzenie fasady,
która zapewnia interfejs bardziej ogólny zapewniający
interfejs całego podsystemu
Fasada
Mamy np. środowisko z podsystemem będącym
kompilatorem. Mamy Parser, Analizator,
WęzełProgramu, …
Dla klienta, który pragnie jedynie skompilować kawałek
programu, nie jest to istotne. Potrzebuje on interfejsu
klasy Kompilator, będącego fasadą dla podsystemu
Fasada
Fasada
Stosowalność
zapewnienie prostego interfejsu do złożonego systemu
stosowanie wzorców rozbija systemy na bardzo wiele małych
klas, co powoduje się bardziej nadają się one do
wielokrotnego użytku oraz są prostsze w rozbudowie
rozrastanie się systemu powoduje natomiast, że klientowi
trudniej jest go wykorzystać
fasada zapewnia prosty, funkcjonalny interfejs, wystarczająco
dobry dla większości klientów, który może pozostać stały
nawet jeśli podsystem wewnątrz uległ zmianie
zwiększa przenośność i niezależność podsystemu
umożliwia układanie systemów warstwami (fasada
definiuje punkty wejścia do podsystemu)
Fasada
Struktura
Fasada
Uczestnicy
Fasada (Kompilator)
Wie jakie klasy podsystemu są odpowiedzialne, za spełnienie
żądania
Przekazuje żądania do odpowiednich obiektów systemu
Klasa podsystemu (Parser, Analizator, …)
Implementuje funkcjonalność systemu
Wykonuje pracę przydzieloną przez fasadę
Nie wie nic o fasadzie
Fasada
Współpraca
Klienci komunikują się z podsystemem, wysyłając
żądania do Fasady, która przekazuje żądania do
podsystemu
(może być zmuszona przetłumaczyć swój interfejs na
interfejsy podsystemu)
Klienci korzystający z fasady nie muszą mieć
bezpośredniego dostępu do obiektów jej podsystemu
Fasada
Konsekwencje
Mniejsza liczba obiektów, z którymi klient ma do
czynienia
(łatwość użycia)
Mogą eliminować złożone lub cykliczne zależności
między obiektami systemu
Większa przenośność na inne platformy
(przy jej użyciu jest mniejsze prawdopodobieństwo, że
zmiana w jednym podsystemie spowoduje konieczność
przebudowy pozostałych
Pełnomocnik
Przeznaczenie
zapewnia reprezentanta innego obiektu w celu
sterowania dostępem do obiektu oryginalnego
Pełnomocnik
Uzasadnienie stosowania
Jednym z powodów sterowania dostępem do obiektu jest
np. chęć odłożenia na później tworzenia/inicjowania
tego obiektu
np. wielkie obrazy rastowe w dokumencie
Otwieranie dokumentu powinno być szybkie i dopiero
kiedy obiekty stają się widoczne powinniśmy je
budować…
Powinno być to przezroczyste dla obiektu dokumentu
Dlatego w miejsce rysunku tworzymy Pełnomocnika,
który dopiero buduje obraz na żądanie wyświetlenia go
Pełnomocnik
Pełnomocnik przechowując dodatkowo np. rozmiary
obrazka może spełniać żądania programu formatującego
bez potrzeby sięgania do egzemplarza rysunku
Aby korzystać z tych udogodnień wystarczy do
istniejącej struktury klas dodać pełnomocnika:
Pełnomocnik
Pełnomocnik
Stosowalność
zawsze, gdy potrzeba uniwersalnego sposobu odwołania
do obiektu
(bardziej wyrafinowanego niż zwykły wskaźnik)
np.
Pełnomocnik zdalny
lokalny reprezentant obiektu z innej przestrzeni
adresowej
Pełnomocnik wirtualny
tworzy kosztowne obiekty na żądanie (np.
PełnomocnikRysunku)
Pełnomocnik ochraniający
kontroluje dostęp do obiektu
zapewnia różne prawa dla różnych obiektów
Pełnomocnik
Sprytny wskaźnik
zastępuje zwykły wskaźnik, wykonując dodatkowe akcje
przy dostępie do obiektu, jak:
zliczanie liczby odwołań do obiektu (usuwanie przy
braku odwołań)
Ładowanie trwałych obiektów do pamięci przy
pierwszym odwołaniu
Pełnomocnik
Struktura
Pełnomocnik
Uczestnicy
Pełnomocnik (PełnomocnikRysunku)
Przechowuje odwołanie, umożliwiające mu dostęp do
prawdziwego obiektu
Zapewnia identyczny interfejs z oryginalnym obiektem
(może wystąpić wszędzie tam, gdzie oryginał)
Kontroluje dostęp do obiektu
… (cokolwiek)
Przedmiot (ObiektGraficzny)
Definiuje wspólny interfejs dla PrawdziwegoPrzedmiotu i
Pełnomocnika
PrawdziwyPrzedmiot (Rysunek)
Definiuje dowolny przedmiot
Pełnomocnik
Współpraca
Pełnomocnik jeśli trzeba przekazuje żądania do
PrawdziwegoPrzedmiotu (w zależności od rodzaju
pełnomocnika)
Pełnomocnik
Konsekwencje
dodatkowy poziom pośredniości dostępu do obiektu,
może:
ukryć fakt, że obiekt jest zdalny
wykonywać optymalizacje jak tworzenie obiektu
na żądanie
dodatkowe czynności porządkowe
ukrywanie np. kopiowania-przy-zapisywaniu
odłożenie na później kopiowania obiektu – będzie on
rzeczywiście skopiowany tylko jeśli użytkownika zażąda
operacji modyfikującej kopię
Pyłek
Przeznaczenie
wykorzystuje współdzielenie obiektów, w celu
efektywnej obsługi wielkiej liczby drobnych
obiektów
Pyłek
Uzasadnienie stosowania
Większość edytorów używa obiektów do
przechowywania takich elementów jak tabele, rysunki
Na ogół jednak powstrzymują się od stosowania
obiektów do reprezentacji poszczególnych znaków w
dokumencie, mimo że przyczyniłoby to się do
zwiększenia elastyczności (nowe zestawy znaków,
jednakowe traktowanie znaków i innych obiektów pod
względem formatowania i wypisywania
Przyczyną tego jest koszt pamięciowy takiego
rozwiązania!
Tutaj pojawia się Pyłek
Pyłek
Jest on współdzielonym obiektem, który może być
używany jednocześnie w wielu kontekstach
Działa on jako obiekt niezależny w każdym kontekście
(nie może niczego zakładać o kontekście działania)
Jest nieodróżnialny od egzemplarza obiektu, który nie
jest współdzielony
Edytor może utworzyć Pyłek dla każdej litery alfabetu
Każdy Pyłek przechowuje wtedy tylko kod znaku, jego
miejsce i styl typograficzny natomiast są mu dostarczane
przez algorytmy rozmieszczania i formatowania tekstu
Pyłek
Logiczny punkt widzenia:
1 znak = 1 obiekt
Fizycznie istnieje jednak jeden współdzielony obiekt dla
każdej litery:
Pyłek
Struktura klas takich obiektów wygląda tak:
Stan zewnętrzny musi być przekazywany jako parametr
do niektórych operacji (dotyczących wystąpień znaków
w dokumencie)
Pyłek
Przechowywanie stanu zewnętrznego jest zadaniem
Klienta, może być ono zaimplementowany w postaci
BDrzewa:
Węzły wewnętrzne = zakresy indeksów glifów
Liście = style
Pyłek
Stosowalność
System używa wielkiej liczby obiektów
Koszty pamięciowe są wysokie ze względu na samą
tylko liczbę obiektów
Większość stanu obiektów może być przeniesiona na
zewnątrz
Po usunięciu stanu zewnętrznego wiele grup obiektów
można zastąpić stosunkowo niewielką liczbą
współdzielonych obiektów
Tożsamość obiektów nie jest istotna w systemie
Pyłek
Struktura
Pyłek
Uczestnicy
Pyłek (Glif)
Deklaruje interfejs, przez który pyłki mogą otrzymywać stan
zewnętrzny i działać zgodnie z nim
PyłekKonkretny (Znak)
Implementuje interfejs pyłku i dodaje pamięć do
przechowywania stanu wewnętrznego
Musi dawać się współdzielić (niezależność od kontekstu w
jakim występuje)
FabrykaPyłków
Tworzy obiekty klasy Pyłek i zarządza nimi
Zapewnia właściwe współdzielenie pyłków
Klient
Utrzymuje odwołania do pyłków
Przechowuje stan zewnętrzny pyłków
Pyłek
Współpraca
Stan którego pyłek potrzebuje do działania musi być
scharakteryzowany albo jako wewnętrzny albo
zewnętrzny. Stan zewnętrzny jest dostarczany przez
klienta
Klienci nie powinni bezpośrednio tworzyć egzemplarzy
klasy PyłekKonkretny (otrzymują je z FabrykiPyłków)
Pyłek
Konsekwencje
Zwiększenie czasu wykonywania w związku z
przesyłaniem lub wyliczaniem stanu zewnętrznego
Oszczędność pamięci
Zmniejszenie łącznej liczby egzemplarzy (współdzielenie)
Wielkość stanu wewnętrznego obiektu
Wyliczanie/przechowywanie stanu zewnętrznego
Często łączony z wzorcem Kompozyt w celu przedstawienia
struktury hierarchicznej ze współdzielonymi węzłami/liśćmi
(problem = stan wewnętrzny nie może zawierać wskaźnika do
rodzica; rozwiązanie = rodzic przekazywany jako część stanu
zewnętrznego)
Stan
Przeznaczenie
Umożliwia obiektowi zmianę zachowania, gdy
zmienia się jego stan wewnętrzny
Obiekt wydaje się zmieniać wówczas swoją klasę
Stan
Uzasadnienie stosowania
Rozważmy klasę PołączenieTCP, reprezentującą
połączenie sieciowe:
Ilekroć połączenie zmienia stan, obiekt PołącznieTCP
zmienia swój obiekt-stan => zmienia przez to swoje
zachowanie
Stan
Stosowalność
Zachowanie obiektu zależy od jego stanu, a obiekt musi
zmieniać swoje zachowanie w czasie wykonywania
programu
Operacje zawierają duże, skomplikowane instrukcje
warunkowe, zależne od stanu obiektu
Stan przenosi każde rozgałęzienie warunkowe do
oddzielnej klasy
Stan
Struktura
Stan
Uczestnicy
Kontekst (PołączenieTCP)
Definiuje interfejs dla Klientów
Utrzymuje egzemplarz podklasy StanuKonkretnego,
definiujący bieżący stan
Stan (StanTCP)
Definiuje interfejs do kapsułkowania zachowania związanego
ze stanem Kontekstu
Podklasa StanuKonkretnego (TCPNawiazane)
Każda implementuje zachowanie związane ze stanem
Kontekstu
Stan
Współpraca
Kontekst przekazuje żądania specyficzne dla stanu do
obiektu StanKonkretny
(być może przekazuje również siebie jako parametr)
Klient konfiguruje Kontekst początkowym stanem, a
następnie nie musi zajmować się bezpośrednio
obiektami Stan
O wyborze kolejnego stanu decyduje albo Kontekst,
albo podklasy StanuKonkretnego
Stan
Konsekwencje
Całe zachowanie dotyczące stanu w jednym obiekcie
Dużo większa liczba klas
Zachowanie mniej zwarte – korzystne gdy wiele stanów
(inaczej duże instrukcje warunkowe)
Jawność przejść między stanami
(coś więcej niż tylko przypisanie kilku zmiennych)
Atomowe przejścia między stanami (zmiana 1 zmiennej)
Możliwość współdzielenia obiektów Stan
Stan
Kto definiuje przejścia? (tego nie ustala wzorzec)
bardziej elastyczne wydaje się umożliwienie podklasom
Stanu samodzielnego ustalenia ich następników
wprowadza to niestety zależności implementacyjne (Stany
muszą o sobie wiedzieć), co może prowadzić do problemów
w dużych systemach z wieloma stanami
Stan
Przykłady
Wiele programów do rysowania zapewnia różnorodne
„narzędzia”:
narzędzie do rysowania
narzędzie do zaznaczania
W zależności od tego, które narzędzie jest aktywne
operacje myszką wywołują inne efekty
Można tu zastosować wzorzec Stanu do reprezentacji
wybranego narzędzia
Stan
Bibliografia
Gamma, Helm, Johnson, Vlissides „Wzorce
projektowe”, WNT 2005.