Szkolenie dla NaviExpert,

Slides:



Advertisements
Podobne prezentacje
C++ wykład 2 ( ) Klasy i obiekty.
Advertisements

Programowanie obiektowe
POWIAT MYŚLENICKI Tytuł Projektu: Poprawa płynności ruchu w centrum Myślenic poprzez przebudowę skrzyżowań dróg powiatowych K 1935 i K 1967na rondo.
Wzorce.
Zaawansowane metody programowania – Wykład V
Generics w .NET 2.0 Łukasz Rzeszot.
Obiektowe metody projektowania systemów Design Patterns STRATEGY.
Domy Na Wodzie - metoda na wlasne M
Nguyen Hung Son Uniwersytet Warszawski
Programowanie obiektowe w Javie
Wzorce projektowe Paweł Ciach.
W ZORCE P ROJEKTOWE … czyli ktoś już rozwiązał Twoje problemy!
Szkolenie dla NaviExpert, Wprowadzenie.
Projektowanie oprogramowania
Organizacja Przedsięwzięć Programistycznych Projektowanie
Obiektowe metody projektowania systemów
Obiektowe metody projektowania systemów
Obiektowe metody projektowania systemów Command Pattern.
Prezentacja poziomu rozwoju gmin, które nie korzystały z FS w 2006 roku. Eugeniusz Sobczak Politechnika Warszawska KNS i A Wykorzystanie Funduszy.
Zasady zaliczenia Warunki uzyskania zaliczenia:
Enteprise Java Beans Emil Wcisło.
Wzorce projektowe w J2EE
Projektowanie i programowanie obiektowe II - Wykład IV
Język Java Wielowątkowość.
Wzorce projektowe (Design Patterns)
Klamki do drzwi Klamki okienne i inne akcesoria
Matura 2005 Wyniki Jarosław Drzeżdżon Matura 2005 V LO w Gdańsku
Wykonawcy:Magdalena Bęczkowska Łukasz Maliszewski Piotr Kwiatek Piotr Litwiniuk Paweł Głębocki.
Ogólnopolski Konkurs Wiedzy Biblijnej Analiza wyników IV i V edycji Michał M. Stępień
Projektowanie obiektowe
Źródła: podręcznikopracował: A. Jędryczkowski.
Projektowanie obiektowe
Dziedziczenie Maciek Mięczakowski
Projektowanie obiektowe
Projektowanie obiektowe
Projektowanie obiektowe
Projektowanie obiektowe
1. Pomyśl sobie liczbę dwucyfrową (Na przykład: 62)
Analiza matury 2013 Opracowała Bernardeta Wójtowicz.
Programowanie obiektowe – język C++
Programowanie obiektowe 2013/2014
ZWIĄZKI MIĘDZY KLASAMI KLASY ABSTRAKCYJNE OGRANICZENIA INTERFEJSY SZABLONY safa Michał Telus.
Spływ należności w Branży Elektrycznej
Wstępna analiza egzaminu gimnazjalnego.
EGZAMINU GIMNAZJALNEGO 2013
EcoCondens Kompakt BBK 7-22 E.
EcoCondens BBS 2,9-28 E.
User experience studio Użyteczna biblioteka Teraźniejszość i przyszłość informacji naukowej.
WYNIKI EGZAMINU MATURALNEGO W ZESPOLE SZKÓŁ TECHNICZNYCH
UML W V ISUAL S TUDIO Mateusz Lamparski. UML D EFINICJA Unified Modeling Language (UML) to graficzny język do obrazowania, specyfikowania, tworzenia i.
Testogranie TESTOGRANIE Bogdana Berezy.
Jak Jaś parował skarpetki Andrzej Majkowski 1 informatyka +
Diagram klas Kluczowymi elementami są: klasy (class)
Współrzędnościowe maszyny pomiarowe
Elementy geometryczne i relacje
Strategia pomiaru.
LO ŁobżenicaWojewództwoPowiat pilski 2011r.75,81%75,29%65,1% 2012r.92,98%80,19%72,26% 2013r.89,29%80,49%74,37% 2014r.76,47%69,89%63,58% ZDAWALNOŚĆ.
Zakres Wzorce projektowe ( -Adapter (str , wykład wzorce projektowe.
Obiektowe metody projektowania systemów Abstract Factory design pattern (aka. Kit)
Zakres Wzorce projektowe - kreacyjne -Factory Method -Abstract Factory.
Paweł Starzyk Obiektowe metody projektowania systemów
Wzorce Projektowe w JAVA
Programowanie Zaawansowane
Inżynieria oprogramowania Wzorce konstrukcyjne WWW: Jacek Matulewski Instytut Fizyki, UMK.
Wzorce projektowe Szkolenie InMoST 21 lutego 2006 Bartosz Walter.
Programowanie Obiektowe – Wykład 6
Wątki, programowanie współbieżne
(według:
Programowanie Obiektowe – Wykład 2
PGO Interfejsy Michail Mokkas.
Zapis prezentacji:

Szkolenie dla NaviExpert, 21.02.2011 Bartosz Walter Szkolenie dla NaviExpert, 21.02.2011 Wybrane wzorce projektowe Gang of Four Wzorce projektowe cz. I

Agenda Motywacja dla stosowania i definiowania wzorców Bartosz Walter Agenda Motywacja dla stosowania i definiowania wzorców Struktura wzorca projektowego Katalog wzorców projektowych Wykład jest pierwszym z trzech poświęconych wzorcom projektowym. Podczas niego zostanie przedstawiona motywacja dla stosowania wzorców, typowy szablon wzorca oraz pierwsza część katalogu wzorców autorstwa tzw. Bandy Czterech. Wzorce projektowe cz. I

Motywacja Różne dziedziny inżynierii stawiają sobie podobne pytania: Bartosz Walter Motywacja Różne dziedziny inżynierii stawiają sobie podobne pytania: Czy typowe problemy można rozwiązywać w powtarzalny sposób? Czy te problemy można przedstawić w sposób abstrakcyjny, tak aby były pomocne w tworzeniu rozwiązań w różnych konkretnych kontekstach? Dążenia do jednolitości rozwiązań, ich klasyfikacji i uproszczenia, pojawiają w wielu dziedzinach inżynierii. Podstawowe pytanie dotyczy możliwości wielokrotnego wykorzystania raz sformułowanego rozwiązania danego problemu. Czy można zapisać to rozwiązanie w sposób ogólny, abstrahując od szczegółowych rozwiązań i jednocześnie umożliwiając wielokrotne jego wykorzystanie? Wzorce projektowe cz. I

Bartosz Walter Geneza wzorców „Wzorzec opisuje problem, który powtarza się wielokrotnie w danym środowisku, oraz podaje istotę jego rozwiązania w taki sposób, aby można było je zastosować miliony razy bez potrzeby powtarzania tej samej pracy” Christopher Alexander „A pattern language”, 1977 Pojęcie wzorca pojawiło się po raz pierwszy w architekturze. Jego twórcą był architekt, Christopher Alexander, który postawił pytanie, czy estetyka i funkcjonalność budowli i przestrzeni jest wartością obiektywną, wynikającą ze stosowania określonych rozwiązań, czy też każdorazowo zależy od pojedynczej koncepcji. Uznał, że wartości te można opisać za pomocą reguł, mówiących, że w celu osiągnięcia określonego celu należy zastosować pewne rozwiązanie, które pociąga za sobą określone konsekwencje. Jest on także autorem pierwszej definicji wzorca, która jest na tyle ogólna, że można ją nadal stosować w oderwaniu od pierwotnej dziedziny zastosowań, czyli architektury. Mówi ona o problemie, kontekście, w jakim jest on osadzony, oraz szkielecie rozwiązania opisanym ogólnie, na wysokim poziomie abstrakcji. Taki wzorzec, po nadaniu wartości zmiennym, jest gotowym rozwiązaniem znajdującym zastosowanie w konkretnej sytuacji. Wzorce projektowe cz. I

Wzorce w budownictwie lądowym Bartosz Walter Wzorce w budownictwie lądowym Czy zbudować most, opierając przęsło na kolejnych filarach połączonych łukiem, tak aby łuk usztywniał przęsło, stanowiąc jego podparcie na całej długości przęsła, czy też mocując przęsło z obu stron za pomocą lin stalowych o kolejno coraz krótszych długościach do pylonów umieszczonych pośrodku długości mostu? Aby przybliżyć pojęcie wzorca, przyjrzyjmy się dylematowi projektanta budowlanego, który opisuje alternatywne sposoby konstrukcji mostu. Z każdym rozwiązaniem związane są pewne wymagania wstępne, uwarunkowania konstrukcyjne i konsekwencje. Wyrażenie ich w sposób opisowy jest możliwe, ale dość skomplikowane i narażone na pomyłki. Trzeba bowiem niejako na nowo przemyśleć poszczególne elementy projektu, uwzględnić zadania, jakie stoją przed projektowaną budowlą, warunki klimatyczne etc. na podstawie przykładu R. Johnsona Wzorce projektowe cz. I

Wzorce w budownictwie lądowym Bartosz Walter Wzorce w budownictwie lądowym Czy zbudować most łukowy czy podwieszany? Dlatego łatwiejsze jest posłużenie się wzorcem, w którym zawarte są gotowe informacje o możliwości zastosowania go w konkretnej sytuacji i efektach takiego rozwiązania. Mosty łukowe i mosty podwieszane wymagają innego rodzaju podparcia, pozwalają na osiągnięcie innej długości przęsła oraz inaczej rozkładają działające siły. W zależności od miejscowych warunków, szerokości rzeki i innych czynników można dokonać wyboru między tymi konkurencyjnymi rozwiązaniami. na podstawie przykładu R. Johnsona Wzorce projektowe cz. I

Wzorce w inżynierii oprogramowania Bartosz Walter Wzorce w inżynierii oprogramowania Wzorce w inżynierii oprogramowania wzorce architektoniczne – poziom integracji komponentów wzorce projektowe – poziom interakcji między klasami wzorce analityczne – poziom opisu rzeczywistości wzorce implementacyjne – poziom języka programowania Wzorzec projektowy identyfikuje i opisuje pewną abstrakcję, której poziom znajduje się powyżej poziomu abstrakcji pojedynczej klasy, instancji lub komponentu. E. Gamma, R. Johnson, R. Helm, J. Vlissides, 1994 Wzorce projektowe stanowiły pierwszy objaw „wzorcomanii” w inżynierii oprogramowania. Dążenie do półformalnego opisania wiedzy na temat „dobrych rozwiązań” znajduje coraz szerszy oddźwięk w społeczności badaczy i praktyków wytwarzania oprogramowania. Obecnie pojęcie wzorca jest często wykorzystywane w wielu innych zastosowaniach, także na poziomie architektury, testowania, analizy i implementacji oprogramowania. Wzorce projektowe cz. I

Systematyka wzorców projektowych Bartosz Walter Systematyka wzorców projektowych Wzorce kreacyjne abstrakcyjne metody tworzenia obiektów uniezależnienie systemu od sposobu tworzenia obiektów Wzorce strukturalne sposób wiązania obiektów w struktury właściwe wykorzystanie dziedziczenia i kompozycji Wzorce behawioralne algorytmy i przydział odpowiedzialności opis przepływu kontroli i interakcji Pierwszą szeroko znaną publikacją na temat wzorców była książka autorstwa E. Gammy, R. Helma, R. Johnsona i J. Vlissidesa, znanych także jako Banda Czterech (ang. Gang of Four). Autorzy książki zaproponowali podstawowy podział wzorców na trzy kategorie: wzorce kreacyjne (ang. creational), dotyczące tworzenia obiektów lub struktur obiektowych, wzorce strukturalne (ang. structural), opisujące sposób wiązania obiektów w złożone struktury o określonych właściwościach, oraz wzorce behawioralne (ang. behavioral), opisujące algorytmy realizacji typowych zadań. Wzorce projektowe cz. I

Szablon wzorca projektowego Bartosz Walter Szablon wzorca projektowego Wzorzec projektowy jest opisany przez: nazwę – lakoniczny opis istoty wzorca klasyfikację – kategorię, do której wzorzec należy cel – do czego wzorzec służy aliasy – inne nazwy, pod którymi jest znany motywację – scenariusz opisujący problem i rozwiązanie zastosowania – sytuacje, w których wzorzec jest stosowany strukturę – graficzną reprezentację klas składowych wzorca Każdy wzorzec należący do katalogu zaproponowanego przez „Bandę Czterech” opisany jest przez zestaw atrybutów, dzięki którym jego właściwości są przedstawione w usystematyzowany, powtarzalny i obiektywny sposób. W ten sposób powstał szablon wzorca projektowego. Podczas wykładu jednak każdy wzorzec zostanie opisany tylko przez część atrybutów, w zakresie pozwalającym poznać przeznaczenie wzorca i istotę jego konstrukcji. Szczegółowego opisu można szukać w literaturze. Nazwa wzorca jest dobrana tak, aby szybko nasuwać skojarzenia z przeznaczeniem wzorca. Nazwy pierwotnie zostały sformułowane po angielsku, i tak też będą używane w trakcie wykładu. Stosowanie spójnego, anglojęzycznego nazewnictwa pozwala na łatwą komunikację, dlatego unikanie polskich tłumaczeń wydaje się uzasadnione. Cel wzorca krótko opisuje kontekst, w jakim go warto zastosować, i jakie efekty można przy jego pomocy osiągnąć. Bardzo ważnym elementem jest opis struktury wzorca, przede wszystkim w zakresie powiązań pomiędzy uczestniczącymi w nim klasami w postaci diagramu klas UML. Aspekt dynamiczny opisywany jest w atrybucie dotyczącym kolaboracji. Wzorce projektowe cz. I

Szablon wzorca projektowego cd. Bartosz Walter Szablon wzorca projektowego cd. uczestników – nazwy i odpowiedzialności klas składowych wzorca współdziałania – opis współpracy między uczestnikami konsekwencje – efekty zastosowania wzorca implementację – opis implementacji wzorca w danym języku przykład – kod stosujący wzorzec pokrewne wzorce – wzorce używane w podobnym kontekście Lista uczestników wzorca zawiera nie tylko nazwy ról klas wchodzących w jego skład, ale także zakres ich odpowiedzialności. Jest to uszczegółowienie informacji, które znajdują się na diagramie struktury. Często pomijaną składową każdego wzorca jest informacja o konsekwencjach, jakie niesie jego zastosowanie, szczególnie negatywnych. Wykorzystanie wzorca często narzuca pewne decyzje, dlatego projektant powinien być świadomy ich związków z tym wzorcem. Przykład pozwala lepiej zrozumieć charakter, przeznaczenie i strukturę wzorca. Wzorce projektowe cz. I

Katalog wzorców projektowych Bartosz Walter Katalog wzorców projektowych Katalog wzorców projektowych Gang of Four (Gamma, Johnson, Helm, Vlissides) obejmuje 23 wzorce: kreacyjne: Abstract Factory, Builder, Factory Method, Prototype, Singleton strukturalne: Adapter, Bridge, Composite, Decorator, Composite, Facade, Proxy, Flyweight behawioralne: Chain of Responsibility, Command, Interpreter, Mediator, Iterator, Memento, Observer, State, Strategy, Template Method, Visitor Lista wzorców jest sukcesywne uzupełniana przez innych autorów Katalog przedstawiony w książce Bandy czterech składa się z 24 wzorców, z których 5 należy do kategorii wzorców kreacyjnych, 8 – strukturalnych, a 11 – behawioralnych. Podczas wykładu zostanie przedstawionych 23 wzorce należących do kanonicznego katalogu (pominięty zostanie wzorzec Interpreter, z uwagi na ograniczone zastosowania). Dodatkowo zostanie omówiony nie należący kanonu wzorzec puli obiektów. Wzorce projektowe cz. I

Bartosz Walter Singleton: cel Zapewnienie, że klasa posiada jedną instancję wewnątrz całej aplikacji Stworzenie punktu dostępowego do tej instancji Singleton jest najprostszym wzorcem projektowym. Jego celem jest stworzenie obiektowej alternatywy dla zmiennych globalnych, nieobecnych w wielu językach obiektowych: zapewnienie istnienia w aplikacji tylko jednej instancji danej klasy oraz udostępnienie tej instancji w łatwo dostępny i intuicyjny sposób, zwykle poprzez dedykowaną metodę statyczną. Gang of Four Wzorce projektowe cz. I

Singleton: struktura i uczestnicy Bartosz Walter Singleton: struktura i uczestnicy Singleton definiuje statyczną metodę getInstance() udostępniającą instancję klasy ogranicza dostęp do konstruktora do własnej klasy i podklas jest odpowiedzialny za tworzenie instancji własnej klasy Singleton składa się z jednej klasy, która zarządza swoją własną jedyną instancją. Instancja jest przechowywana w postaci prywatnego pola statycznego, natomiast zarządzaniem nią zajmuje się publiczna metoda statyczna o nazwie getInstance(). Postępuje ona według następującego algorytmu: jeżeli pole statyczne przechowujące instancję klasy ma wartość null (czyli instancja dotąd nie została utworzona), wówczas instancja taka jest tworzona i zapamiętywana w tym polu. Dzięki temu, niezależnie od tego, który raz wywoływana jest metoda, zawsze zwraca ona utworzoną i jedyną instancję klasy. Aby uniemożliwić klientom samodzielne tworzenie instancji z pominięciem metody statycznej, klasa Singleton uniemożliwia dostęp do konstruktora z zewnątrz, zwykle czyniąc go prywatnym lub chronionym. Wzorce projektowe cz. I

Singleton: konsekwencje Bartosz Walter Singleton: konsekwencje Singleton przejmuje odpowiedzialność za tworzenie instancji własnej klasy Klient nie zarządza instancją klasy; otrzymuje ją na żądanie Singleton może zarządzać także swoimi podklasami Singleton można łatwo rozszerzyć do puli obiektów Singleton jest zwykle obiektem bezstanowym Singleton zachowuje się podobnie do zmiennej globalnej Singleton może powodować zwiększenie liczby powiązań w systemie Singleton jest przede wszystkim obiektowym sposobem na zapewnienie, że zostanie utworzona dokładnie jedna instancja klasy, która będzie dostępna dla wszystkich obiektów aplikacji. Warto zauważyć, że ten wzorzec pozwala także przenieść odpowiedzialność za tworzenie obiektu z klienta na dedykowaną metodę. Koncepcja ta zostanie dalej rozwinięta we wzorcach Factory Method i Abstract Factory. Singleton jest zwykle obiektem bezstanowym, tzn. sposób działania metody statycznej nie zależy od stanu, w jakim znajduje się program: klient otrzymuje instancję klasy na żądanie, niezależnie od tego, czy została ona utworzona wcześniej, czy nie. Singleton pozwala także stosować dziedziczenie w celu zmiany przez siebie tworzonej klasy i zwracać także instancje podklas. Dołączenie podklasy do wzorca nie wymaga modyfikacji po stronie klienta. Singleton w pewnym sensie może także być uważany za szczególny przypadek obiektu Pool of Objects; może także być stosunkowo łatwo rozszerzony do takiej postaci. Wzorce projektowe cz. I

Singleton: implementacja 2PL Bartosz Walter Singleton: implementacja 2PL static public Tax getInstance() { if (instance == null) { synchronize (this) { instance == new TaxA(); } return instance; W języku Java implementacja tego wzorca napotyka na wiele trudności ze względu na sposób wykonywania programów i konstrukcję maszyny wirtualnej, w której są uruchamiane programy. M.in. w programie wielowątkowym istnieje możliwość, że wskutek przerwania wykonywania metody w momencie sprawdzania, czy instancja obiektu została już utworzona, kontrolę przejmie drugi wątek, który utworzy swoją własną instancję. W celu rozwiązania tego problemu można zastosować zmodyfikowaną wersję algorytmu blokowania dwufazowego (2PL). Zakłada ona, że istnienie instancji obiektu jest sprawdzane dwukrotnie: na zewnątrz i wewnątrz bloku synchronizacji, w którym instancja ta jest tworzona. Taka konstrukcja, mimo pewnego narzutu związanego z synchronizacją wątków, pozwala uniknąć utworzenia wielu instancji klasy. Istnienie obiektu instance jest sprawdzane dwukrotnie, na zewnątrz i wewnątrz bloku synchronizacji Shalloway & Trott (2001) Wzorce projektowe cz. I

Singleton: implementacja z class loaderami Bartosz Walter Singleton: implementacja z class loaderami public class TaxA extends Tax { private static class Instance { static final Tax instance = new TaxA(); } private TaxA() {} public static Taxt getInstance() { return Instance.instance; Inne rozwiązania wykorzystuje mechanizm działania tzw. class loader’ów wewnątrz maszyny wirtualnej. Obiekty class loader służą do ładowania klas i są zorganizowane w postaci drzewa. Każdy z nich, otrzymując żądanie załadowania klasy, aby uniknąć wielokrotnego załadowania tej samej klasy, zawsze najpierw konsultuje się ze swoim nadrzędnym class loaderem, czy nie załadował on już poszukiwanej klasy. W ten sposób poprawnie napisane class loadery (mogą one być definiowane przez programistę) zapewniają, że w maszynie wirtualnej zawsze znajduje się co najwyżej jedna reprezentacja danej klasy. Wzorzec może być wówczas zaimplementowany w postaci instancję klasy TaxA w statycznej klasie wewnętrznej Instance. Instancja ta jest tworzona w momencie załadowania klasy TaxA (oraz Instance) do maszyny wirtualnej, a sposób działania obiektu class loader zapewnia, że nie zostanie utworzona więcej niż jedna jej instancja. Rozwiązanie to działa poprawnie, o ile obiekty class loader zdefiniowane przez programistę zachowują się poprawnie, tj. konsultują ładowanie każdej klasy ze swoim nadrzędnym class loaderem. Jeżeli ta zasada zostanie naruszona, wówczas nadal istnieje niebezpieczeństwo utworzenia wielu instancji. Class loader ładuje pojedynczą klasę TaxA.Instance, która przechowuje pojedynczą instancję klasy Tax Shalloway & Trott (2001) Wzorce projektowe cz. I

Bartosz Walter Pool of Objects: cel Zarządzanie grupą obiektów reprezentujących zasoby wielokrotnego użycia Ograniczenie kosztów tworzenia i usuwania obiektów Pula obiektów stanowi pewnego rodzaju rozszerzenie idei wzorca Singleton oraz opisanego dalej wzorca Factory Method: pozwala na przesunięcie odpowiedzialności za tworzenie produktów na oddzielny obiekt, a jednocześnie umożliwia wielokrotne wykorzystanie poszczególnych instancji obiektów. Ma to szczególne znaczenie w przypadku produktów reprezentujących zasoby, które są czasowo alokowane na rzecz konkretnego klienta. Pozwala to istotnie ograniczyć koszt związany z tworzeniem i usuwaniem obiektów. Shalloway & Trott (2001) Wzorce projektowe cz. I

Pool of Objects: struktura Bartosz Walter Pool of Objects: struktura Najważniejszym elementem wzorca jest klasa Pool, która w porównaniu do wymienionych wcześniej wzorców Singleton i FactoryMethod ma zwiększony zakres odpowiedzialności. Nie tylko zajmuje się tworzeniem instancji klasy ReusableObject, ale także zarządzaniem cyklem życia już utworzonych obiektów. Najczęściej klasa ta utrzymuje zbiór aktywnych obiektów ReusableObject, które są przekazywane klientom na żądanie i przyjmowane od nich z powrotem po wykorzystaniu. Zatem klasa Pool posiada interfejs służący do tworzenia produktu (metoda getInstance()) oraz ich zwracania (metoda returnInstance()). Z punktu widzenia klienta obiekt klasy Pool jest fabryką produktów, ponieważ klient nie musi zajmować się ich tworzeniem, zarządzaniem, odtwarzaniem etc. Wzorce projektowe cz. I

Pool of Objects: uczestnicy Bartosz Walter Pool of Objects: uczestnicy Pool definiuje punkt dostępu do obiektów Reusable Object zarządza cyklem życia obiektów Reusable Object Reusable Object definiuje swój cykl życia może być powtórnie wykorzystany Client otrzymuje obiekty Reusable Object za pośrednictwem obiektu Pool Najważniejsze dwie funkcje obiektu Pool to zdefiniowanie punktu dostępu (zarówno tworzenia, jak i zwrotu) do obiektów typu ReusableObject, oraz zarządzanie cyklem ich życia. Cykl życia produktu składa się zwykle z fazy inicjalizacji, obsługi i finalizacji. Ponieważ klient oczekuje produktu gotowego do natychmiastowego użytku, dlatego fazy inicjalizacji i finalizacji są pod kontrolą obiektu Pool. Obiekt ReusableObject musi posiadać zdefiniowany cykl życia: zestaw metod odpowiednio modyfikujących jego stan. Najważniejszą cechą tego obiektu jest możliwość jego ponownego użycia przez innego klienta. Klient żąda obiektu ReusableObject za pomocą obiektu Pool i w ten sam sposób zwalnia przydzielony obiekt. Wzorce projektowe cz. I

Pool of Objects: konsekwencje Bartosz Walter Pool of Objects: konsekwencje Zwiększona wydajność obiekty ReusableObject są tworzone w ograniczonej liczbie instancji i wykorzystywane wielokrotnie zrównoważone obciążenie zasobów Lepsza hermetyzacja klient nie zajmuje się tworzeniem i obsługą obiektów ReusableObject Dzięki wykorzystaniu wzorca Pool of Objects, obiekty ReusableObject są tworzone w ograniczonej liczbie instancji i mogą być następnie wielokrotnie wykorzystywane. Pozwala to usunąć istotny koszt związany z tworzeniem obiektów. Jest on szczególnie dokuczliwy, gdy liczba żądań jest duża, a czas wykorzystania obiektu bardzo krótki, np. w kontenerach Java Servlets obsługujących żądania HTTP. Do każdego żądania jest przydzielana para obiektów reprezentujących żądanie i odpowiedź HTTP. Skalowalność wymaga, aby liczba jednoczesnych żądań wynosiła przynajmniej kilkadziesiąt, czego nie dałoby się osiągnąć bez efektywnego mechanizmu zarządzania pulą obiektów. Innym, często spotykanym przykładem, jest dostęp do bazy danych za pomocą interfejsów JDBC. Za każdym razem wymagane jest udostępnienie klientowi obiektu typu Connection, które na czas operacji na bazie danych musi być związane z jednym wątkiem. Utworzenie obiektu Connection jest bardzo czasochłonne, dlatego zwykle jest on umieszczany w puli, tak aby po jego wykorzystaniu przez jeden wątek mógł on trafić do niej z powrotem. Liczba jednocześnie istniejących obiektów jest konfigurowalna, tak aby zapewnić obsługę wszystkich żądań. Obiekt Pool może także wykorzystywać skomplikowane algorytmy heurystyczne w celu przewidywania zapotrzebowania na obiektu ReusableObject i dostosowywania do potrzeb liczby obiektów przechowywanych w puli. Ponadto wzorzec ten poprawia hermetyzację obiektu ReusableObject: klient nie zajmuje się ich obsługą, a jedynie korzysta z oferowanych przez nie usług. Wzorce projektowe cz. I

Bartosz Walter Observer: cel Utworzenie zależności typu jeden-wiele pomiędzy obiektami Informacja o zmianie stanu wyróżnionego obiektu jest przekazywana wszystkim pozostałym obiektom Wzorzec Observer służy do stworzenia relacji typu jeden-wiele łączącej grupę obiektów. Dzięki niemu zmiana stanu obiektu po stronie „jeden” umożliwi automatyczne powiadomienie o niej wszystkich innych zainteresowanych obiektów (tzw. obserwatorów). Gang of Four Wzorce projektowe cz. I

Observer: struktura Bartosz Walter Wzorce projektowe cz. I Wzorzec składa się z dwóch ról: obiektu obserwowanego (Subject) oraz obserwatorów (Observer). Obiekt Subject posiada metody pozwalające na dołączanie i odłączanie obserwatorów: każdy zainteresowany obiekt może się zarejestrować jako obserwator. Ponadto posiada metodę notify(), służącą do powiadamiania wszystkich zarejestrowanych obserwatorów poprzez wywołanie w pętli na ich rzecz metody update(). Interfejs Observer jest bardzo prosty i zawiera tylko jedną metodę – update(). Metoda ta jest wykorzystywana właśnie do powiadamiania obiektu o zmianie stanu obiektu obserwowanego, a sam interfejs jest jedyną informacją, jaką o obserwatorach posiada ten obiekt. Wzorce projektowe cz. I

Observer: uczestnicy Subject utrzymuje rejestr obiektów Observer Bartosz Walter Observer: uczestnicy Subject utrzymuje rejestr obiektów Observer umożliwia dołączanie i odłączanie obiektów Observer Observer udostępnia interfejs do powiadamiania o zmianach Concrete Subject przechowuje stan istotny dla obiektów Concrete Observer powiadamia obiekty Concrete Observer Concrete Observer aktualizuje swój stan na podstawie powiadomienia W ramach wymienionych dwóch podstawowych dwóch ról: obserwatora i obiektu obserwowanego, można wydzielić dodatkowo warstwę abstrakcji i warstwę implementacji. W tej pierwszej znajdują się interfejsy Subject i Observer, które definiują zakres funkcjonalności poszczególnych klas, oraz klasy ConcreteSubject i ConcreteObserver, które są przykładami realizacji tych kontraktów. W języku Java rola obiektu obserwowanego jest reprezentowana przez klasę java.util.Observable, natomiast obserwatory implementują interfejs java.util.Observer. Dzięki temu implementacja wzorca w tym języku jest znacznie uproszczonym zadaniem. Wzorce projektowe cz. I

Observer: konsekwencje Bartosz Walter Observer: konsekwencje Luźniejsze powiązania pomiędzy obiektami: obiekt Subject komunikuje się z innymi obiektami przez interfejs Observer obiekty Subject i Observers mogą należeć do różnych warstw abstrakcji Programowe rozgłaszanie komunikatów Spójność stanu pomiędzy obiektami Subject i Observers Skalowalność aktualizacji push: Observers otrzymują kompletny stan obiektu Subject pull: Observers otrzymują powiadomienie i referencję do obiektu Subject Wzorzec Observer pozwala na znaczne ograniczenie powiązań i zależności pomiędzy obserwatorami i obiektem obserwowanym. Wprawdzie obiekt obserwowany posiada referencje do obserwatorów, jednak wiedza jest ograniczona tylko do znajomości interfejsu Observer. Także obserwatory nie muszą znać obiektu Subject w momencie wywołania ich metody update(), ponieważ otrzymują powiadomienia asynchroniczne. Dzięki ogólności interfejsu Observer obiekty uczestniczące we wzorcu mogą należeć do różnych warstw abstrakcji. Wzorzec pozwala zachować spójność pomiędzy warstwami aplikacji, ponieważ informacje o zmianach w jednej warstwie są przekazywane natychmiast do pozostałych obiektów. Jest to szczególnie często jest wykorzystywane do komunikacji w wielu systemach okienkowych. Zamiennie zamiast nazwy Observer wykorzystuje się nazwę Listener. Ponieważ ilość informacji przekazywanych obiektom Observer może istotnie wpływać na wydajność systemu, dlatego istnieją dwa podejścia do implementacji tego wzorca. W modelu push każdy obserwator otrzymuje w postaci parametru metody update() pełną informację o stanie obiektu Subject. W modelu pull obserwatory otrzymują tylko referencję do obiektu Subject, dzięki której mogą następnie odpytać go o szczegóły dotyczące zmiany. Ten ostatni model jest zatem znacznie lepiej skalowalny, szczególnie w przypadku wywoływania tych metod w środowisku rozproszonym. Wzorce projektowe cz. I

Adapter: cel Umożliwienie współpracy obiektów o niezgodnych typach Bartosz Walter Adapter: cel Umożliwienie współpracy obiektów o niezgodnych typach Tłumaczenie protokołów obiektowych Adapter (znany także pod nazwą Wrapper) służy do adaptacji interfejsów obiektowych, tak aby możliwa była współpraca obiektów o niezgodnych typach. Szczególnie istotną rolę odgrywa on w przypadku wykorzystania gotowych bibliotek o interfejsach niezgodnych ze stosowanymi w aplikacji. Gang of Four Wzorce projektowe cz. I

Adapter: struktura Bartosz Walter Wzorce projektowe cz. I Struktura wzorca składa się z trzech podstawowych klas: Target, Adaptee oraz Adapter. Target jest interfejsem, którego oczekuje klient. Obiektem dostarczającym żądanej przez klienta funkcjonalności, ale niezgodnego pod względem typu, jest Adaptee. Rolą Adaptera, który implementuje typ Target, jest przetłumaczenie wywołania metody należącej do typu Target poprzez wykonanie innej metody (lub grupy metod) w klasie Adaptee. Dzięki temu klient współpracuje z obiektem Adapter o akceptowanym przez siebie interfejsie Target, jednocześnie wykorzystując funkcjonalność dostarczoną przez Adaptee. Alternatywna nazwa wzorca – Wrapper, która oznacza opakowanie, bardzo dobrze opisuje rolę obiektu Adapter: pełnić wobec Klienta rolę otoczki, która umożliwia przetłumaczenie jego żądań na protokół zrozumiały dla faktycznego wykonawcy poleceń. Wzorzec ten posiada także wersję wykorzystującą dziedziczenie w relacji Adapter-Adaptee. Jednak wersja ta ma pewne niedogodności: powiązania między obiektami są ustalane w momencie kompilacji i nie mogą ulec zmianie; ponadto, język programowania musi umożliwiać stosowanie wielokrotnego dziedziczenia lub dziedziczenia i implementacji interfejsu (jak w przypadku języków Java i C#). Wzorce projektowe cz. I

Adapter: uczestnicy Target definiuje interfejs specyficzny dla klienta Bartosz Walter Adapter: uczestnicy Target definiuje interfejs specyficzny dla klienta Client współpracuje z obiektami typu Target Adaptee posiada interfejs wymagający adaptacji Adapter adaptuje interfejs Adaptee do interfejsu Target We wzorcu występują trzy podstawowe obiekty: Target, definiujący interfejs wymagany przez klienta, i poprzez który chce on wykorzystywać określoną funkcjonalność, Adaptee, który posiada tę funkcjonalność, ale jest niezgodny pod względem typu z interfejsem Target, oraz Adapter, dokonujący translacji pomiędzy nimi. Wzorce projektowe cz. I

Adapter: konsekwencje Bartosz Walter Adapter: konsekwencje Duża elastyczność pojedynczy Adapter może współpracować z wieloma obiektami Adaptee naraz Adapter może dodawać funkcjonalność do Adaptee (zob. wzorzec Decorator) Utrudnione pokrywanie metod Adaptera konieczne utworzenie podklas obiektu Adaptee i bezpośrednie odwołania do nich Kompozycja i dziedziczenie jako mechanizmy adaptacji Adapter, niezależnie od swojego podstawowego przeznaczenia, wprowadza dodatkową warstwę abstrakcji, która pozwala uniknąć bezpośredniej zależności pomiędzy klientem a obiektem wykonującym żądania. Dzięki temu relację pomiędzy nimi można traktować w sposób elastyczny, np. zmieniając liczbę aktywnych obiektów Adaptee, którymi zarządza jeden Adapter. Wzorzec może alternatywnie wykorzystywać dwa rodzaje relacji: kompozycję i dziedziczenie; użycie tej pierwszej daje więcej możliwości modyfikacji systemu w przyszłości. Możliwa jest również rozbudowa tego wzorca do wzorca Decorator, tzn. rozszerzenie funkcjonalności obiektu Adaptee w Adapterze. Wzorce projektowe cz. I

Jednolita obsługa pojednczych obiektów i złożonych struktur Bartosz Walter Composite: cel Organizowanie obiektów w struktury drzewiaste reprezentujące relacje typu całość-część Jednolita obsługa pojednczych obiektów i złożonych struktur Composite jest bardzo często stosowanym wzorcem służącym do reprezentacji struktur drzewiastych typu całość-część tak, aby sposób zarządzania strukturą nie zależał od jej złożoności. Jest często stosowany w obiektowych bibliotekach okienkowych jako metoda zarządzania widokami zbudowanymi z wielu widget’ów. Gang of Four Wzorce projektowe cz. I

Composite: struktura Bartosz Walter Wzorce projektowe cz. I Centralnym elementem wzorca jest interfejs Component, który reprezentuje dowolny obiekt w strukturze drzewiastej. Posiada on możliwości dodawania i usuwania swojego obiektu potomnego (oczywiście, także typu Component) oraz odwołania się do wybranego potomka. Zawiera on także metodę operation(), którą należy wykonać na każdym węźle struktury. Interfejs Component posiada dwie implementacje: Leaf oraz Composite. Klasa Leaf reprezentuje obiekty, które nie posiadają potomków (czyli liście w strukturze), natomiast Composite jest dowolnym węzłem pośrednim. Ponieważ każdy węzeł pośredni zarządza także poddrzewem, którego jest korzeniem, dlatego metoda operation(), poza wykonaniem operacji specyficznych dla każdego węzła, wywołuje swoje odpowiedniki w obiektach potomnych, w ten sposób propagując wywołanie. Z punktu widzenia klienta taka struktura umożliwia zarządzanie całością za pomocą jednego obiektu – korzenia drzewa. Niepotrzebna jest także wiedza o rozmiarze drzewa, ponieważ wywołanie zostanie przekazane automatycznie do wszystkich jego elementów. Wzorce projektowe cz. I

Composite: uczestnicy Bartosz Walter Composite: uczestnicy Component deklaruje wspólny interfejs dla obiektów znajdujących się strukturze implementuje wspólną funkcjonalność wszystkich obiektów Leaf reprezentuje węzeł bez potomków Composite reprezentuje węzeł z potomkami przechowuje referencje do potomków deleguje otrzymane polecenia do potomków Component, podstawowy element wzorca, przede wszystkim deklaruje wspólny interfejs dla wszystkich obiektów. Jego implementacje, Leaf i Composite, reprezentują odpowiednio węzły bez potomków i węzły pośrednie. Wzorce projektowe cz. I

Composite: konsekwencje Bartosz Walter Composite: konsekwencje Elastyczna definicja struktur drzewiastych Proste dodawanie nowych komponentów Proste i spójne zarządzanie strukturą o dowolnej liczbie elementów Mechanizm ten jest jednym z najczęściej wykorzystywanych wzorców projektowych, np. w systemach okienkowych. Strukturę drzewiastą tworzą wówczas składowe okienek: przyciski, etykiety, listy etc. Popularność tego wzorca wynika z elastycznego zarządzania złożonymi strukturami z punktu widzenia klienta: nie jest wymagana wiedza o rozmiarze i dokładnej strukturze drzewa. Ponadto wszystkie elementy struktury realizują ten sam algorytm, co znacznie ułatwia ich testowanie. Wzorce projektowe cz. I

Bartosz Walter Proxy: cel Dostarczenie zamiennika dla obiektu w celu jego kontroli i ochrony Przezroczyste odsunięcie inicjalizacji obiektu w czasie Celem wzorca Proxy jest zastąpienie obiektu docelowego tymczasowym substytutem, który może pełnić trzy funkcje: odsunie w czasie moment utworzenia obiektu docelowego, będzie kontrolował do niego dostęp lub pozwoli odwoływać się do obiektu zdalnego. Z punktu widzenia klienta substytut powinien być przezroczysty i nie może mieć wpływu na sposób interakcji z obiektem docelowym. Gang of Four Wzorce projektowe cz. I

Proxy: struktura Bartosz Walter Wzorce projektowe cz. I Centralnym elementem wzorca jest interfejs Subject, który posiada wiele implementacji. Jedną z nich jest obiekt RealSubject – obiekt docelowy posiadający funkcjonalność wymaganą przez klienta. Drugą – obiekt proxy, który posiada referencję do obiektu RealSubject i kontroluje do niego dostęp. Celem takiego powiązania obiektów jest umożliwienie zastąpienia obiektu docelowego obiektem Proxy: klient, zamiast do obiektu docelowego, odwołuje się do obiektu Proxy, który deleguje żądania do niego lub próbuje obsługiwać je samodzielnie. W szczególności obiekt Proxy może utworzyć obiekt RealSubject znacznie później niż klient może korzystać z niego, a tym samym opóźnić inicjację tego obiektu. Pozwala to m.in. na oszczędność czasu i innych zasobów. Wzorce projektowe cz. I

Proxy: uczestnicy Proxy Bartosz Walter Proxy: uczestnicy Proxy posiada referencję do obiektu Real Subject i deleguje do niego żądania kontroluje dostęp do obiektu Real Subject jest zamiennikiem Real Subject dla klienta Subject definiuje wspólny interfejs dla Proxy i Real Subject Real Subject rzeczywisty obiekt wymagający kontroli i ochrony Obiekt Proxy pełni główną rolę we wzorcu: zarządza podległym mu obiektem RealSubject i podejmuje decyzje dotyczące utworzenia go, przekazania mu sterowania etc. W ten sposób pełni funkcje ochronne (uniemożliwia nieautoryzowany dostęp) oraz kontrolne w stosunku do niego. Subject defniuje wspólny interfejs, poprzez który odbywa się wymiana komunikatów między klientem a układem Proxy – RealSubject. Wzorce projektowe cz. I

Bartosz Walter Proxy: konsekwencje Zdalny obiekt Proxy jest lokalnym reprezentantem obiektu znajdującego się w innej przestrzeni adresowej Wirtualny obiekt Proxy pełni rolę zamiennika dla obiektu o dużch wymaganiach zasobowych (np. pamięciowych) Ochronny obiekt Proxy odostępnia obiekt Real Subject tylko uprawnionym obiektom Istnieją trzy podstawowe rodzaje wzorca Proxy: Zdalny obiekt Proxy (ang. remote proxy) służy do reprezentacji obiektu znajdującego się w innej przestrzeni adresowej, np. na innym komputerze. Dzięki temu dla lokalnych klientów wszystkie odwołania są pozornie lokalne. Proxy przejmuje wówczas odpowiedzialność za zdalne wywołania metod poprzez sieć, serializację parametrów i odebranie wyników. Mechanizm ten jest stosowany w większości środowisk przetwarzania rozproszonego np. CORBA lub EJB. Wirtualny obiekt Proxy zastępuje obiekt RealSubject o dużych wymaganiach zasobowych, np. alokujący duży obszar pamięci. Aby opóźnić (a w szczególnych przypadkach nawet zastąpić) proces tworzenia takiego obiektu, Proxy obsługuje wszystkie zadania obiektu RealSubject, które nie wymagają odwołań do tego obszaru pamięci. Ochronny obiekt Proxy zajmuje się zabezpieczeniem dostępu do obiektu RealSubject przed nieautoryzowanym dostępem. Obiekt RealSubject nigdy nie jest bezpośrednio dostępny dla klientów; w ich imieniu występuje Proxy, który określa, którym z nich można udostępnić usługi oferowane przez RealSubject, a którym nie. Wzorce projektowe cz. I

Command: cel Hermetyzacja poleceń do wykonania w postaci obiektów Bartosz Walter Command: cel Hermetyzacja poleceń do wykonania w postaci obiektów Umożliwienie parametryzacji klientów obiektami poleceń Wsparcie dla poleceń odwracalnych Wzorzec Command pozwala hermetyzować polecenia do wykonania w postaci obiektów, aby można było traktować je w sposób abstrakcyjny i np. przekazywać jako parametry. W języku C istnieje możliwość przekazania wskaźnika na funkcję. W wysokopoziomowych językach obiektowych, które tej możliwości nie posiadają, ten sam efekt można osiągnąć poprzez przekazanie referencji lub wskaźnika do obiektu definiującego określoną metodę. Takie rozwiązanie zapewnia hermetyzację poleceń, możliwość abstrahowania od ich przeznaczenia, a przy okazji umożliwia stosowanie np. poleceń odwracalnych (o ile obiekt reprezentujący polecenie zapamiętuje stan sprzed jego wykonania). E. Gamma et al. (1995) Wzorce projektowe cz. I

Command: struktura Bartosz Walter Wzorce projektowe cz. I Podstawowym elementem wzorca jest interfejs Command, deklarujący metodę execute(). Jest to polimorficzna metoda reprezentująca polecenie do wykonania. Metoda ta jest implementowana w klasach ConcreteCommand w postaci polecenia wykonania określonej akcji na obiekcie-przedmiocie Receiver. Warto zauważyć, że klient nie jest bezpośrednio związany ani z obiektem Command, ani z obiektem inicjującym jego wywołanie, czyli Invoker. Widzi jedynie odbiorcę wyników operacji – obiekt Receiver. Wzorce projektowe cz. I

Command: interakcje Bartosz Walter Wzorce projektowe cz. I Szczegółowy przepływ sterowania przedstawia diagram sekwencji. Inicjatorem przetwarzania jest obiekt Invoker, który zarządza obiektami typu Command. W momencie nadejścia żądania wykonania określonej operacji Invoker parametryzuje skojarzony z nią obiekt Command właściwym odbiorcą ich działań, czyli obiektem Receiver. Następnie wywołuje metodę execute() w tym obiekcie, powodując określone skutki w obiekcie Receiver, widoczne dla Klienta. Wzorce projektowe cz. I

Command: uczestnicy Command Bartosz Walter Command: uczestnicy Command definiuje interfejs obiektu reprezentującego polecenie Concrete Command jest powiązany z właściwym obiektem Receiver implementuje akcję w postaci metody execute() Client tworzy Concrete Command Invoker ustala odbiorcę akcji każdego obiektu Command wywołuje metodę execute() obiektu Command Receiver jest przedmiotem akcji wykonanej przez Command Role poszczególnych obiektów zostaną omówione na przykładzie. W aplikacji okienkowej polecenia znajdujące się w menu są zdefiniowane w postaci obiektów typu Command. Każde polecenie jest inną implementacją tego interfejsu, i posiada innego odbiorcę, ustalanego w momencie wykonywania akcji (np. polecenie zamknięcia okna działa na aktualnie aktywne okno). W momencie kliknięcia na wybranej pozycji menu (czyli obiektu Invoker), wykonuje ona metodę execute() skojarzonego z nią polecenia typu Command, ustalając jego odbiorcę. Efekt, w postaci np. zamknięcia okna, jest widoczny dla klienta. Wzorce projektowe cz. I

Command: konsekwencje Bartosz Walter Command: konsekwencje Usunięcie powiązania między nadawcą i przedmiotem polecenia Łatwe dodawanie kolejnych obiektów Command Możliwość manipulacji obiektami Command polecenia złożone: wzorzec Composite Polecenia mogą być odwracalne zapamiętanie stanu przez Concrete Command wykorzystanie wzorca Memento Istotną korzyścią płynącą z zastosowania wzorca jest rozdzielenie zależności pomiędzy nadawcą (Klientem) i odbiorcą (obiektem Receiver) komunikatu. Zastosowanie polimorfizmu pozwala traktować poszczególne polecenia abstrakcyjnie, a co za tym idzie – dodawać nowe typy poleceń bez konieczności zmiany struktury systemu. Poszczególne obiekty Command mogą być dowolnie złożone, także w postaci kompozytów innych poleceń. Dodatkową zaletą użycia obiektu do hermetyzacji poleceń jest możliwość utworzenia w typie Command przeciwstawnej metody, która odwraca efekt wykonania polecenia. W takiej sytuacji obiekt ConcreteCommand musi zapamiętać stan obiektu Receiver sprzed wykonania operacji lub np. skorzystać z wzorca Memento. Wzorce projektowe cz. I

Command: przykład Bartosz Walter Wzorce projektowe cz. I Bank zarządza grupą obiektów Account reprezentujących rachunki bankowe. Operacje bankowe, wykonywane na rachunkach, są implementacjami interfejsu Operation, posiadającego metodę execute(). Jej implementacja zależy od rodzaju operacji, dlatego w przypadku obiektu InterestChange będzie ona zmieniała stopę procentową, a w przypadku obiektu Transfer – dokonywała przelewu. Ponieważ każda operacja wymaga innych parametrów, dlatego są one przekazywane w konstruktorze poszczególnej klasy, a nie bezpośrednio w metodzie execute(). W tym przykładzie rolę obiektu Invoker pełni bank, ponieważ on wykonuje metodę execute(), a rolę przedmiotu polecenia (obiektu Receiver) – obiekt Account. Wzorce projektowe cz. I

Command: przykład cd. public class Bank { // Invoker, Client Bartosz Walter Command: przykład cd. public class Bank { // Invoker, Client public void income(Account acc, long amount) { Operation oper = new Income(amount); acc.doOperation(oper); } public void transfer(Account from, Account to, long amount){ Operation oper = new Transfer(to, amount); from.doOperation(oper); public class Account { // Reciever long balance = 0; Interest interest = new InterestA(); History history = new History(); public void doOperation(Operation oper) { oper.execute(this); history.log(oper); Na slajdzie przedstawiono przykładową implementację klasy Bank, która pełni role Invoker i Client, oraz klasy Account, będącej odbiorcą poleceń. Klasa Bank definiuje metodę income(), która służy do wykonywania wpłaty na określony rachunek. W tym celu tworzy on instancję odpowiedniej operacji (klasy Income), a następnie przekazuje jej wykonanie obiektowi Account. Klasa Account wykonuje dowolną abstrakcyjną operację przekazaną z zewnątrz, np. przez klasę Bank. Dzięki temu dodanie nowej operacji bankowej nie powoduje konieczności jakiejkolwiek zmiany w klasie Account. Wzorce projektowe cz. I

Command: przykład cd. abstract public class Operation { // Command Bartosz Walter Command: przykład cd. abstract public class Operation { // Command public void execute(); } public class Income { // ConcreteCommand1 public Income(long amount) { // store parameters... public void execute(Account acc) { acc.add(amount); public class Transfer { // ConcreteCommand2 public Income(Account to, long amount) { public void execute(Account from) { from.subtract(amount); to.add(amount); Klasa Operation pełni rolę obiektu Command we wzorcu i definiuje abstrakcyjną metodę execute(). Jest ona pokrywana w klasach reprezentujących poszczególne operacje bankowe, które implementują ją zgodnie ze specyfiką wykonywanej operacji. Wzorce projektowe cz. I

Factory Method: cel Zdefiniowanie interfejsu do tworzenia obiektów Bartosz Walter Factory Method: cel Zdefiniowanie interfejsu do tworzenia obiektów Umożliwienie przekazania odpowiedzialności za tworzenie obiektów do podklas Umożliwienie wyboru klasy i konstruktora użytego do utworzenia obiektu Wzorzec Factory Method jest podstawowym wzorcem kreacyjnym. Jego celem jest zastąpienie prostych wywołań konstruktora dedykowanym interfejsem (metodą), która przejmie odpowiedzialność za tworzenie i ew. inicjację obiektu danej klasy. Podobnie jak w przypadku wzorca Singleton (który jest specjalizowaną wersją Factory Method, ograniczoną do tworzenia jednego obiektu), istnieje możliwość hermetyzacji wewnątrz tej metody sposobu wyboru klasy obiektu spośród jej podklas oraz użytego konstruktora. E. Gamma et al. (1995) Wzorce projektowe cz. II

Factory Method: struktura Bartosz Walter Factory Method: struktura Klient odwołuje się do dwóch interfejsów (lub klas abstrakcyjnych): Creator, zawierającej metodę tworzącą produkty, i Product, reprezentującą obiekty tworzone przez Factory Method. Oba interfejsy posiadają implementacje powiązane parami: obiekt klasy ConcreteCreator o przeciążonej metodzie factoryMethod() tworzy instancję obiektu ConcreteProduct. Dzięki temu, podczas zmiany obiektu Creator, jednocześnie zmieniany jest tworzony produkt. Warto zwrócić na dualizm hierarchii klas produktu i kreatora, który pozwala na abstrakcyjne traktowanie całego procesu tworzenia obiektów za pomocą wymiennych producentów, co jest niemożliwe przy bezpośrednim użyciu konstruktora. Z punktu widzenia klienta metoda factoryMethod() jest równoważna pod względem funkcjonalnym z konstruktorem: jej wywołanie powoduje utworzenie obiektu żądanego typu. Warto zwrócić uwagę, że wzorzec ten pozwala także na dodatkowy stopień swobody: bezpośrednie wywołanie konstruktora przez klienta zawsze powoduje utworzenie obiektu konkretnej klasy (konstruktory nie są metodami polimorficznymi), natomiast użycie wzorca Factory Method pozwala metodzie tworzącej obiekty na wybór klasy obiektu i sposobu jego tworzenia. Wzorce projektowe cz. II

Factory Method: uczestnicy Bartosz Walter Factory Method: uczestnicy Product definiuje interfejs obiektów tworzonych przez Factory Method Concrete Product specyficzny produkt tworzony przez Factory Method Creator definiuje interfejs do tworzenia obiektów typu Product Concrete Creator tworzy obiekt typu Concrete Product Product reprezentuje wszystkie obiekty, jakie są tworzone przez metodę factoryMethod(). Często jest to grupa klas posiadająca wspólną nadklasę lub zwykły interfejs z implementującymi go klasami. Klient jest powiązany z produktami właśnie poprzez ten interfejs. Tworzeniem produktów zajmują się obiekty o interfejsie Creator. W podstawowej postaci wzorca Interfejs ten także jest jedyną informacją dotyczącą typu, jaką posiada klient. Użycie odpowiedniej klasy ConcreteCreator determinuje klasę i właściwości produktu, jaki zostanie utworzony. W innej wersji tego wzorca Creator jest klasą, której statyczna metoda factoryMethod() dokonuje selekcji produktów na podstawie przekazanych jej parametrów. Wzorce projektowe cz. II

Factory Method: konsekwencje Bartosz Walter Factory Method: konsekwencje Przeniesienie odpowiedzialności za tworzenie obiektów Product z klienta na obiekt Creator Możliwość rozszerzania hierarchii klas Product niezależnie od klienta Najważniejszym efektem użycia wzorca jest przeniesienie odpowiedzialności za tworzenie obiektów klasy Product z klienta na obiekt klasy Creator. Dzięki temu klient może założyć, że za każdym razem, gdy wywoła metodę factoryMethod(), otrzyma instancję klasy gotową do użycia. Ponadto wzorzec umożliwia tworzenie nie tylko instancji jednej klasy, ale całych ich hierarchii, z możliwością wyboru klasy i użytego konstruktora. Bezpośrednie wywołanie konstruktora nie daje takiej możliwości. Wzorce projektowe cz. II

Bartosz Walter Abstract Factory: cel Stworzenie interfejsu do tworzenia grup powiązanych ze sobą produktów Rozszerzenie Factory Method na grupy produktów Wzorzec Abstract Factory jest rozszerzeniem koncepcji znanej z Factory Method na całą rodzinę produktów, której zmiana na inną powinna odbywać się w postaci jednego kroku. Celem wzorca jest zdefiniowanie interfejsu do tworzenia takich rodzin obiektów, korzystając (podobnie jak w przypadku poprzedniego wzorca) z dedykowanych klas zajmujących się produkcją obiektów. E. Gamma et al. (1995) Wzorce projektowe cz. II

Abstract Factory: struktura Bartosz Walter Abstract Factory: struktura Klient, analogicznie do rozwiązania stosowanego w Factory Method, odwołuje się do klasy AbstractFactory służącej do tworzenia grupy różnych produktów. Każdy produkt jest tworzony przez osobną metodę, która sama stosuje wzorzec Factory Method. AbstractFactory jest klasą abstrakcyjną, tzn. nie definiuje, w jaki sposób mają być tworzone odpowiednie produkty; deklaruje jedynie obecność odpowiedzialnych za to metod. Tworzeniem konkretnych produktów zajmują się jej implementacje, w których każda z metod tworzy odpowiedni obiekt należący do typu danego produktu. Klient postrzega instancje produktów wyłącznie poprzez zdefiniowane interfejsy AbstractProdyct. Poniżej warstwy abstrakcji znajdują się implementacje tych interfejsów (np. ProductA1 i ProductA2) które są tworzone właśnie przez obiekty ConcreteFactory. Klient, wybierając odpowiednią fabrykę ConcreteFactory, decyduje jednocześnie o wyborze całej rodziny Produktów. To pozwala zmieniać całe rodziny produktów w prosty sposób – posługując się inną implementacją fabryki. Wzorce projektowe cz. II

Abstract Factory: uczestnicy Bartosz Walter Abstract Factory: uczestnicy Abstract Factory definiuje interfejs do tworzenia obiektów Abstract Product Concrete Factory tworzy obiekty Concrete Product należące do jednej grupy Abstract Product deklaruje interfejs obiektów Product Concrete Product definiuje obiekt Product Interfejs AbstractFactory definiuje osobne metody typu factoryMethod() dla każdego typu produktu, jaki ma tworzyć. Produkty nie są w żaden sposób ze sobą związane. Dopiero obiekt ConcreteFactory, będący implementacją klasy AbstractFactory, określa, jakie konkretne klasy zostaną użyte do konstrukcji produktów. Klasy produktów również są widziane jako interfejsy AbstractProduct. Dzięki temu zmiana rodziny produktów jest przezroczysta z punktu widzenia klienta. Wzorce projektowe cz. II

Abstract Factory: konsekwencje Bartosz Walter Abstract Factory: konsekwencje Łatwa zmiana całych grup produktów poprzez zmianę używanej Concrete Factory Wydzielenie interfejsu do tworzenia obiektów Odseparowanie klienta od szczegółów implementacji obiektów Product Utrudnione dodawanie kolejnych obiektów Product we wszystkich grupach Zastosowanie tego wzorca pozwala w łatwy sposób zmieniać całe rodziny produktów, zmieniając tylko ich fabrykę. Ponadto, struktura wzorca pozwala łatwo wydzielić warstwę abstrakcji i implementacji, i to zarówno w przypadku fabryki, jak i produktu. Szczegóły implementacyjne obu typów klas są więc niewidoczne dla klienta, co przyczynia się do większej elastyczności systemu. Dodawanie kolejnych rodzin produktów wiąże się z koniecznością zaimplementowania także nowej fabryki, która będzie dostarczać wspomniane produkty. W ten sposób obiekt fabryki i tworzone przez niego obiekty są związane ze sobą i tworzą hermetyczną całość. Należy jednak pamiętać, że dodanie do wzorca kolejnego typu Product jest utrudnione, ponieważ wymaga modyfikacji wszystkich istniejących dotychczas fabryk. Dlatego wzorzec ten stosuje się w sytuacjach, w których zestaw produktów jest zamknięty. Wzorzec ten jest stosowany m.in. w bibliotece Java Swing do reprezentacji tzw. skórek (czyli mechanizmu umożliwiającego szybką zmianę wyglądu interfejsu użytkownika). Wszystkie implementacje okienek, przycisków, list i innych elementów GUI są produkowane przez wybraną fabrykę. Zmiana implementacji tej fabryki oznacza jednoczeną modyfikację zawartości ekranu. Wzorce projektowe cz. II

Chain of Responsibility: cel Bartosz Walter Chain of Responsibility: cel Usunięcie powiązania pomiędzy nadawcą i odbiorcą żądania Umożliwienie wielu obiektom obsługi żądania Wzorzec Chain of Responsibility jest strukturą, która definiuje łańcuch obiektów będących potencjalnymi odbiorcami i wykonawcami żądań klienta. Dzięki temu wiele obiektów ma możliwość obsługi żądania, a powiązania pomiędzy nadawcą i odbiorcą (jak i poszczególnymi potencjalnymi odbiorcami) stają się znacznie osłabione lub zostają usunięte. E. Gamma et al. (1995) Wzorce projektowe cz. II

Chain of Responsibility: struktura Bartosz Walter Chain of Responsibility: struktura Struktura tego wzorca jest bardzo prosta: obiekty typu Handler są powiązane ze sobą w postaci jednokierunkowej kolejki (albo łańcucha). Nadchodzące od klienta żądanie jest przekazywane wzdłuż tego łańcucha, gdzie każdy obiekt typu Handler ma szansę na ich obsłużenie. Co ważne, obiekty typu Handler są od siebie niezależne, tzn. nie wiedzą o sobie nic (poza abstrakcyjnym wskazaniem na obiekt następnika). Obiekty Handler tworzą listę jednokierunkową (łańcuch), wzdłuż której są przekazywane żądania. Wzorce projektowe cz. II

Chain of Responsibility: uczestnicy Bartosz Walter Chain of Responsibility: uczestnicy Handler definiuje interfejs do obsługi żądań Concrete Handler obsługuje jeden rodzaj żądania, pozostałe przekazuje do następnika w łańcuchu posiada referencję typu Handler do następnika Client inicjuje przetwarzanie, przekazując żądanie do pierwszego obiektu Handler w łańcuchu Handler definiuje interfejs obsługi żądań. Zwykle jest to jedna metoda, która realizuje prosty algorytm: jeżeli dany obiekt ConcreteHandler jest w stanie obsłużyć żądanie, to obsługuje je; w przeciwnym wypadku (bądź w sytuacji, gdy wiele obiektów typu Handler może obsłużyć jedno żądanie) – przekazuje je do swojego następnika w łańcuchu. Charakterystyczna dla wzorca jest dowolna konfigurowalność łańcucha: żaden jego element nie musi posiadać wiedzy o rodzaju żądań obsługiwanych przez kolejne elementy, dlatego zmiany w jego strukturze nie mają wpływu na zachowanie. Zadaniem klienta przy takiej strukturze jest przekazanie żądania pierwszemu elementowi łańcucha, który następnie dalej obsługuje żądanie. Wzorce projektowe cz. II

Chain of Responsibility: konsekwencje Bartosz Walter Chain of Responsibility: konsekwencje Ograniczone powiązania Klient i każdy obiekt Handler nie wiedzą, który z pozostałych obiektów Handler obsługuje dany typ żądania nadawca i odbiorca żądania nie mają o sobie żadnej wiedzy Możliwość elastycznego przydziału odpowiedzialności do obiektów Handler Ułatwione testowanie Brak gwarancji obsłużenia żądania Zaletą tego wzorca jest znaczne ograniczenie powiązań pomiędzy klientem i każdym z obiektów Handler. Klient, przekazując żądanie, nie wie, który z obiektów Handler będzie je w rzeczywistości obsługiwał. Poszczególne ogniwa łańcucha są zorganizowane w postaci prostej kolejki jednokierunkowej, a ich wiedza o sobie nawzajem ogranicza się do abstrakcyjnego typu ogniwa. Nie znają swoich zadań ani klas, jakie implementują. Taka struktura pozwala elastycznie przydzielać odpowiedzialność do poszczególnych ogniw: każdy z nich zajmuje się obsługą żądań jednego typu, a rozszerzenie łańcucha o kolejne elementy nie wpływa na sposób przetwarzania przez niego żądań. To z kolei przyczynia się do łatwiejszego testowania każdego ogniwa łańcucha z osobna: wystarczy zweryfikować, czy poprawnie obsługuje on żądania jednego typu. Wadą takiej konstrukcji łańcucha jest brak gwarancji obsługi żądania: kolejne ogniwa mogą zrezygnować z zajęcia się nim. Co więcej, informacja o tym fakcie nie jest przekazywana klientowi. W tym celu stosuje się rozmaite rozwiązania pośrednie: umieszczając informację o obsłudze wewnątrz żądania (wówczas brak takiej informacji oznacza jego nieobsłużenie) lub zmieniając nieco strukturę przetwarzania. Ponadto, błąd w implementacji filtra może skutkować nieprzekazaniem sterowania do następnika i przerwaniem łańcucha. Aby zminimalizować to ryzyko, w niektórych implementacjach klasa bazowa Filter posiada zaimplementowany na stałe mechanizm przekazywania sterowania do następnika, a programiście udostępniona jest tylko metoda dokonująca faktycznej obsługi żądania. Wzorce projektowe cz. II

Chain of Responsibility: przykład 1 Bartosz Walter Chain of Responsibility: przykład 1 Prostym przykładem tego wzorca jest np. mechanizm filtrów obecnych w większości klientów poczty elektronicznej. Wiadomość przychodząca do foldera Inbox jest przesyłana przez łańcuch zdefiniowanych przez użytkownika filtrów: każdy z nich może dokonać pewnej akcji na wiadomości, polegającej na przeniesieniu jej do innego foldera, zmianie jej priorytetu czy usunięciu jej. Zasada działania filltrów w takim systemie została przedstawiona na poprzednich slajdach każdy podejmuje decyzję (poprzez wywołanie metody isEligible()), czy konkretna wiadomość powinna być przez niego obsłużona, i przekazuje sterowanie dalej. Obiekt Inbox wywołuje pierwszy obiekt Filter w łańcuchu. Kolejne filtry przekazują sobie sterowanie Wzorce projektowe cz. II

Chain of Responsibility: przykład 2 Bartosz Walter Chain of Responsibility: przykład 2 Z uwagi na wymienione wcześniej niedogodności, przede wszystkim możliwość przerwania łańcucha sterowania, możliwa jest także inna struktura przetwarzania, która nie posiada już topologii łańcucha. W tym rozwiązaniu pojawia się nowa rola: zarządcy, który posiada referencje do wszystkich filtrów. Zarządca (w tym przypadku jest nim także obiekt Inbox) wywołuje po kolei wszystkie filtry, które obsługują daną wiadomość lub nie. Jednak dzięki temu, że filtry nie przekazują sobie bezpośrednio sterowania, nie ma możliwości przerwania łańcucha, a ponadto informacja o nieobsłużeniu żądania może być w łatwy sposób przedstawiona klientowi przez zarządcę. Obiekt Inbox wywołuje kolejno obiekty Filter. Nie występuje bezpośrenie przekazywanie sterowania z jednego filtra do drugiego. Wzorce projektowe cz. II

Bartosz Walter Facade: cel Dostarczenie jednorodnego interfejsu wyższego poziomu do zbioru różnych interfejsów w systemie Ukrycie złożoności podsystemów przed klientem Wzorzec Facade jest prostym wzorcem strukturalnym, który ma na celu stworzenie alternatywnego interfejsu dostępu do grupy podsystemów. Dzięki temu klient jest odseparowany od ich złożoności i ma możliwość wyboru między skomplikowanym interfejsem natywnym (ale za to o pełniejszej funkcjonalności) oraz uproszczonym interfejsem dostarczonym przez wzorzec (realizującym najpotrzebniejszą i najczęściej wykorzystywaną funkcjonalność). E. Gamma et al. (1995) Wzorce projektowe cz. II

Bartosz Walter Facade: struktura W skład wzorca wchodzi klasa (lub kilka klas), stanowiących fasadę grupy podsystemów. Fasada stanowi zatem dodatkową warstwę abstrakcji w dostępie do tych podsystemów i pozwala w łatwiejszy sposób posługiwać się nimi. Należy zwrócić uwagę, że stworzenie obiektu upraszczającego protokół komunikacji z podsystemami zwykle oznacza, że jego funkcjonalność będzie niepełna i ograniczona jedynie do najpopularniejszych operacji. W praktyce takie rozwiązanie jest jednak całkowicie akceptowalne. Podsystemy nie muszą posiadać wiedzy o klasie Facade, natomiast ona musi znać ich strukturę i przeznaczenie. Żądania przesyłane przez klienta fasadzie są przez nią delegowane do odpowiednich podsystemów. Fasada pod względem funkcjonalnym spełnia podobne zadanie co Proxy – pośredniczy w wywoływaniu operacji na faktycznym wykonawcy usług, jednak w odróżnieniu od niego, pozwala także na bezpośrednie odwołania do podsystemów. Klient zatem ma wybór dotyczący sposobu obsługi żądań. Klient może odwołać się zarówno do obiektu Facade, jak i bezpośrednio do podsystemów Wzorce projektowe cz. II

Facade: uczestnicy Facade Bartosz Walter Facade: uczestnicy Facade zna zakres odpowiedzialności poszczególnych podsystemów deleguje żądania klienta do podsystemów subsystems nie wiedzą o obiekcie Facade wykonują żądania od klienta i obiektu Facade We wzorcu uczestniczą obiekty Facade i podsystemy realizujące żądania klienta. Obiekt Facade zna strukturę i powiązania pomiędzy podsystemami, wie także, jak się nimi posługiwać w celu osiągnięcia określonego efektu. W ten sposób problem złożoności podsystemów, ich konfiguracji i interfejsów, który byłby przerzucony na klienta, jest hermetyzowany w postaci fasady. Podsystemy nie są w żaden sposób modyfikowane przez zastosowanie wzorca: ich wiedza i zakres odpowiedzialności nie zmienia się. Wykonują one polecenia zlecanie albo bezpośrednio przez klienta, bądź przez fasadę. Wzorce projektowe cz. II

Facade: konsekwencje Odseparowanie klienta od podsystemów Bartosz Walter Facade: konsekwencje Odseparowanie klienta od podsystemów łatwiejsze korzystanie z podsystemów niższe koszty pielęgnacji podsystemów możliwość wymiany/rozbudowy podsystemów Elastyczny dostęp do podsystemów klient może odwołać się do obiektu Facade lub bezpośrednio do podsystemów Wzorzec ten przede wszystkim ułatwia korzystanie z podsystemów: programista, wywołując odpowiednie metody fasady, nie musi znać szczegółów interfejsu podsystemu, ponieważ komunikacją z nim zajmie się fasada. Zmiany w podsystemach, ich wymiana lub rozbudowa są zatem niewidoczne dla klienta, co obniża koszty ich pielęgnacji. Z drugiej strony, klient ma nadal możliwość wyboru sposobu obsługi żądania pomiędzy fasadą i bezpośrednim skorzystaniem z podsystemów. Wzorce projektowe cz. II

Facade: przykład public class Email { // facade Bartosz Walter Facade: przykład public class Email { // facade MimeMessage msg = null; // podsystem 1 Session session = Session.getInstance(null, props); // podsystem 2 public Email(String subject, String text) { msg = new MimeMessage(session); msg.setFrom(DEFAULT_FROM); msg.setSubject(subject); msg.setText(text, "UTF-8"); } public void sendTo(String[] to) { msg.setRecipients(Message.RecipientType.TO, convert(to)); Transport transport = session.getTransport("smtp"); transport.sendMessage(msg, msg.getAllRecipients() public void sendTo(String[] to, String[] cc) { msg.setRecipients(Message.RecipientType.CC, convert(Cc)); W tym przykładzie klasa Email stanowi fasadę dla protokołu SMTP zaimplementowanego w postaci biblioteki Java Activation Framework. Ustalanie parametrów służących do stworzenia i wysłania wiadomości, ich konwersja do właściwych typów, interpretacja ich znaczenia są dość skomplikowane, dlatego dla najprostszych zastosowań zostały zdefiniowane metody fasady. Użytkownik ma możliwość bezpośredniego posłużenia się podsystemami MimeMessage i Session, albo skorzystać z klasy Email. Wzorce projektowe cz. II

Bartosz Walter Builder: cel Odseparowanie sposobu reprezentacji i metody konstrukcji złożonych struktur obiektowych Wykorzystanie jednego mechanizmu konstrukcyjnego do tworzenia struktur o różnej reprezentacji Builder jest wzorcem strukturalnym i służy do tworzenia złożonych struktur obiektowych. Jego celem jest oddzielenie sposobu reprezentacji tych struktur od mechanizmu ich konstrukcji. Pozwala to także wykorzystać te same mechanizmy konstrukcyjne do tworzenia różnych struktur. E. Gamma et al. (1995) Wzorce projektowe cz. II

Bartosz Walter Builder: struktura Struktura tego wzorca bardzo przypomina podział ról na budowie. Klient odpowiada za zlecenie wykonania prac. Odbiorcą jego zlecenia jest kierownik budowy (Director), który posiada projekt budowlany (algorytm realizacji struktury). Kierownik zna i dysponuje specjalistami od różnych zadań (reprezentowanymi przez klasy implementujące interfejs Builder). Każdy z fachowców, będący swego rodzaju wzorcem Factory, potrafi wykonywać produkty jednego rodzaju i przekazywać je kierownikowi. On, na podstawie projektu, składa elementy stworzone przez fachowców i konstruuje strukturę, a następnie przekazuje ją klientowi. Klient zleca prace, Director zna sposób reprezentacji, a obiekty typu Builder tworzą specjalizowane obiekty typu Product. Wzorce projektowe cz. II

Builder: uczestnicy Builder Bartosz Walter Builder: uczestnicy Builder definiuje interfejs do tworzenia obiektów typu Product Concrete Builder tworzy specjalizowany obiekt typu Product Director zna sposób realizacji struktury i jej algorytm zarządza grupą obiektów Builder i podzleca im wykonanie obiektów Product Product reprezentuje element składowy struktury posiada interfejs umożliwiający łączenie z innymi obiektami Product We wzorcu występuje bardzo wyraźny podział na warstwy różniące się zakresem odpowiedzialności: obiekt Director odpowiada za zarządzanie obiektami typu Builder i zlecanie im prac; nie zajmuje się on jednak bezpośrednią realizacją zadań. Zarządzanie tymi obiektami wymaga, aby znał ich zakres odpowiedzialności, a zatem powiązania między nim a obiektami są dość silne. Ponadto zna on algorytm i sposób reprezentacji docelowej struktury danych, i na tej podstawie zleca prace. Obiekty Builder potrafią wytwarzać produkty: każdy ConcreteBuilder jest związany z produktem, który umie wyprodukować, natomiast nie zajmują się ich kompozycją ani rodzajem struktury. W ten sposób obiekty te mogą być wykorzystane do tworzenia różnych struktur, w zależności od potrzeb. Wszystkie obiekty typu Product posiadają wspólny interfejs, definiujący metody pozwalające łączyć te obiekty w struktury. Wzorce projektowe cz. II

Builder: konsekwencje Bartosz Walter Builder: konsekwencje Zmiana implementacji obiektów Product nie wpływa na proces konstrukcji struktury Odseparowanie reprezentacji i konstrukcji struktur obiektowych Precyzyjna kontrola nad procesem konstrukcji struktury Ułatwione testowanie elementów struktury Wzorzec ten, dzięki przejrzystemu i jednoznacznemu podziałowi odpowiedzialności pomiędzy poszczególne obiekty, zapewnia, że zmiana sposobu implementacji obiektów Product nie wpływa na sam proces konstrukcji. Podobnie, zmiana procesu konstrukcji nie wymaga zmian w implementacji elementów. Istnieje możliwość tworzenia wielu różnych struktur obiektowych bez modyfikacji pozostałych obiektów uczestniczących we wzorcu. Taki podział zwiększa też kontrolę nad procesem konstrukcji struktury, a także umożliwia łatwe testowanie poszczególnych elementów. Wzorce projektowe cz. II

Bartosz Walter Memento: cel Umożliwienie zachowania stanu obiektu na zewnątrz w celu jego późniejszego odtworzenia Zachowanie hermetyzacji tego obiektu Wzorzec Memento umożliwia zapamiętywanie, przechowywanie i odtwarzanie wewnętrznego stanu obiektu. Potrzeba taka często pojawia się w większości aplikacji. Istotą wzorca jest jednak nie zarządzanie samym stanem, ale zapewnienie sposobu bezpiecznego dostępu do niego. E. Gamma et al. (1995) Wzorce projektowe cz. II

Bartosz Walter Memento: struktura Obiektem, którego stan należy przechować, jest Originator. Posiada on metody służące do utworzenia migawki stanu (createMemento()) oraz jej odczytania w celu przywrócenia wcześniejszego stanu (setMemento()). Obiekty-migawki stanu (Memento) przechowują stan obiektu Originator w postaci niezależnych instancji obiektu. Obiekty Memento posiadają metody getState() i setState(), służące do odczytania i zapisania stanu wewnątrz niego. Zarządzaniem kolejnymi migawkami stanu zajmuje się dedykowany obiekt Caretaker. Jednak istotą wzorca nie jest sama możliwość tworzenia migawek stanu, ale zapewnienie im właściwego poziomu bezpieczeństwa. Wzorzec Memento pozwala na dostęp do stanu zapisanego w migawce wyłącznie jego właścicielowi, czyli obiektowi Originator, natomiast inne obiekty (w tym Caretaker) mogą tylko odwoływać się do całych obiektów, a metody setState() i getState() są dla nich niewidoczne. Originator zapisuje i odtwarza swój stan w postaci obiektu Memento. Obiekt Caretaker przechowuje obiekty Memento, ale nie ma dostępu do ich danych Wzorce projektowe cz. II

Memento: uczestnicy Memento Bartosz Walter Memento: uczestnicy Memento przechowuje zapisany stan obiektu Originator uniemożliwia dostęp do tego stanu obiektowi Caretaker Originator tworzy obiekt Memento ze swoim aktualnym stanem odtwarza stan na podstawie obiektu Memento Caretaker przechowuje obiekty Memento nie ma dostępu do ich zawartości Szczególną rolę we wzorcu odgrywają dwie klasy: Originator, który jest twórcą i właścicielem wszystkich migawek stanu, oraz Memento, której obiekty przechowują stan Originatora. Obiekt Originator musi posiadać możliwość utworzenia obiektu Memento oraz odczytania jego zawartości w celu przywrócenia na tej podstawie poprzedniego stanu. Memento przechowuje stan obiektu Originator zapisany w dowolnym momencie; pozwala też na dostęp do niego obiektowi Originator, natomiast uniemożliwia operacje na migawce wszelkim innym obiektom. Przykładem jest obiekt Caretaker, który zarządza utworzonymi migawkami, natomiast nie ma dostępu do ich zawartości. Wzorce projektowe cz. II

Memento: konsekwencje Bartosz Walter Memento: konsekwencje Zachowanie hermetyzacji obiektu Memento Uproszczenie obiektu Originator odpowiedzialność za zapis stanu przeniesiona na Memento Podwójny interfejs obiektu Memento wąski: dla obiektu Caretaker szeroki: dla obiektu Originator Potencjalny wzrost złożoności pamięciowej stan może być obszerny Caretaker nie zna tego rozmiaru i nie może optymalizować sposobu zarządzania nim W ten sposób obiekt Memento posiada dwa logiczne interfejsy: szeroki, umożliwiający pełen dostęp do ich zawartości, przeznaczony wyłącznie dla obiektu Originator, oraz wąski, w praktyce blokujący dostęp do większości metod, przeznaczony dla pozostałych obiektów, w tym obiektu Caretaker. Takie rozwiązanie przede wszystkim zachowuje hermetyczność obiektu Memento, ale również upraszcza obiekt Originator i zmniejsza jego zakres odpowiedzialności. Nie musi już on zajmować się w żaden sposób przechowywaniem migawek stanu, usuwaniem ich etc; Funkcje te zostały wydzielone do obiektów Memento i Caretaker. Pełna hermetyzacja obiektów Memento w stosunku do klasy Caretaker ma także pewne wady: stan przechowywany w tych obiektach może mieć znaczny rozmiar, i zarządzanie nim może wymagać optymalizacji, stosowania heurystycznych algorytmów usuwania niektórych migawek etc. Niestety, ponieważ obiekt Caretaker nie może stwierdzić rozmiaru migawki, nie może również podjąć skutecznego działania w tym kierunku. Wzorce projektowe cz. II

Memento: implementacja Bartosz Walter Memento: implementacja Realizacja podwójnego interfejsu wymaga wsparcia ze strony języka programowania Java: klasy wewnętrzne Klasa wewnętrzna jest znana tylko swojej klasie zewnętrznej C++: klasy zaprzyjaźnione Klasy zaprzyjaźnione są uprzywilejowane w dostępie do swoich składowych niepublicznych Struktura wzorca jest dość oczywista i nie wymaga komentarza. Warto jednak zastanowić się nad sposobem zapewnienia zróżnicowanego dostępu do obiektów Memento dla dwóch różnych klas. Implementacja wzorca w znacznym stopniu zależy od możliwości oferowanych przez język programowania. W przypadku języka C++ może to być zaprzyjaźnienie klas, które pozwala wybranym klasom odwoływać się do swoich składowych jak do elementów prywatnych. Niestety, istnieje grupa języków nie posiadających takich możliwości. W języku Java możliwym rozwiązaniem jest zastosowanie klasy wewnętrznej do reprezentacji obiektu Memento. W ten sposób jedynie jej klasa zewnętrzna posiada dostęp do jej składowych niepublicznych, natomiast inne klasy nie mają o niej żadnej wiedzy. Wzorce projektowe cz. II

Memento: przykład public class Account { private int balance = 0; Bartosz Walter Memento: przykład public class Account { private int balance = 0; public void credit(int amount) { balance += amount; } public void debit(int amount) { balance -= amount; public void setMemento(Memento memento) { memento.restoreState(); public Memento createMemento() { Memento mementoToReturn = new Memento(); mementoToReturn.setState(); return mementoToReturn; Jak przykład rozważmy klasę Account, której stanem jest zmienna balance reprezentująca saldo przechowywane na rachunku bankowym. Poza metodami biznesowymi credit() i debit() klasa ta posiada metodę setMemento(), służącą do odtworzenia stanu na podstawie migawki, oraz createMemento(), tworzącą nową migawkę. Wzorce projektowe cz. II

Bartosz Walter Memento: przykład cd. public class Account { // continued... class Memento { int mementoBalance = 0; private void setState() { mementoBalance = balance; } private void restoreState() { balance = mementoBalance; Wewnątrz klasy Account jest zdefiniowana klasa Memento, posiadająca pole mementoBalance, służące do przechowania wartości salda w danym momencie. Metody setState() oraz restoreState() są widoczne jedynie dla jej nadklasy, natomiast inne obiekty nie mają do nich dostępu. Rolę obiektu Caretaker może pełnić dowolna zmienna typu Account.Memento, która przechowuje instancję migawki. W ten sposób założenia dotyczące podwójnego interfejsu zostały spełnione. Prywatna klasa wewnętrzna pozwala osiągnąć efekt podwójnego interfejsu: tylko klasa zewnętrzna może odwoływać się do jej stanu Wzorce projektowe cz. II

Bartosz Walter Prototype: cel Umożliwienie tworzenia obiektów na podstawie przykładowej instancji, a nie poprzez wywołanie konstruktora Wzorzec Prototype należy do grupy wzorców kreacyjnych, jednak sposób tworzenia przez niego obiektów jest zupełnie inny niż w przypadku innych rozwiązań z tej grupy, np. Factory Method czy Singletona. Celem jego stosowania jest tworzenie nowych obiektów poprzez klonowanie już istniejącego wzorcowego obiektu. E. Gamma et al. (1995) Wzorce projektowe cz. II

Bartosz Walter Prototype: struktura Obiekt poddający się klonowaniu, Prototype, posiada metodę clone(). Metoda ta jest implementowana we wszystkich jego obiektach potomnych w ten sposób, że tworzy ona dokładną kopię bieżącego obiektu. Jedyna różnica pomiędzy oryginałem i klonem polega na odrębnej tożsamości obiektu (w większości języków tożsamość jest rozstrzygana na podstawie referencji do tego obiektu). Klient, żądając utworzenia kopii obiektu Prototype, wywołuje w istniejącej instancji tego obiektu metodę clone(), która zwraca jego klon. Wywołanie metody clone() powoduje utworzenie dokładnej kopii przekazanego obiektu Prototype. Wzorce projektowe cz. II

Prototype: uczestnicy Bartosz Walter Prototype: uczestnicy Prototype deklaruje metodę clone() znacznik obiektów, które mogą się sklonować Concrete Prototype implementuje metodę clone() tworzącą klon własnego obiektu We wzorcu uczestniczy właściwie tylko jedna klasa: Prototype, która posiada możliwość sklonowania obiektów własnej klasy poprzez wywołanie metody clone(). Wzorce projektowe cz. II

Prototype: konsekwencje Bartosz Walter Prototype: konsekwencje Możliwość tworzenia obiektów poprzez przykład Uproszczona konstrukcja podobnych obiektów pominięcie wyboru konstruktora ograniczenie liczby podklas w systemie Najważniejszą konsekwencją zastosowania tego wzorca jest całkowita zmiana sposobu tworzenia obiektów. Typowy sposób polega na podaniu wprost klasy i konstruktora użytego do stworzenia instancji obiektu. Jednak nawet w przypadku wzorca Factory Method oznacza to ograniczenie producenta w zakresie typów obiektów, jakie może stworzyć. Ta niedogodność nie występuje we wzorcu Prototype: dowolny obiekt, jeżeli tylko posiada możliwość sklonowania się, może utworzyć nowy obiekt identyczny ze sobą. Zatem metoda służąca do produkcji obiektów przyjmowałaby jako parametr instancję obiektu do sklonowania, ignorując jego rzeczywistą klasę, i zwracała jego kopię. Dzięki temu możliwe jest uproszczone tworzenie serii obiektów identycznych lub jedynie nieznacznie różniących się od siebie. Wzorce projektowe cz. II

Prototype: przykład public class Employee implements Cloneable { Bartosz Walter Prototype: przykład public class Employee implements Cloneable { private String name = null; public Employee(String name) { this.name = name; } public Object clone() throws CloneNotSupportedException { // tutaj: specyficzne operacje związane z klonowaniem return super.clone(); W języku Java wzorzec ten jest zaimplementowany bezpośrednio w maszynie wirtualnej. Każdy obiekt posiada metodę clone(), a co za tym idzie – potencjalną możliwość klonowania siebie. Jednak aby skorzystać z tej możliwości, konieczne jest zaimplementowanie w wybranej klasie interfejsu Cloneable. Interfejs ten nie definiuje żadnych metod, a jedynie pełni rolę znacznika, wskazującego, że dana klasa posiada uprawnienie do klonowania samej siebie. Próba wywołania tej metody bez zaimplementowania interfejsu powoduje zgłoszenie wyjątku. Domyślnie wywołanie metody clone() powoduje utworzenie tzw. płytkiej kopii obiektu, tzn. obiekty zależne są kopiowane jako referencje, a nie jako obiekty. Płytka kopia jest bezpieczna, ponieważ nie powoduje rekurencyjnego alokowania znacznych obszarów pamięci. Jeżeli istnieje potrzeba realizacji tzw. głębokiej kopii, zadanie jej zaimplementowania leży po stronie programisty. Employee emp = new Employee("John Smith"); Employee emp2 = (Employee) emp.clone(); assertEquals(emp, emp2); Wzorce projektowe cz. II

State/Strategy: cel State Bartosz Walter State/Strategy: cel State umożliwienie zmiany zachowania obiektu w momencie zmiany jego stanu pozorna zmiana klasy obiektu Strategy umożliwienie zmiany algorytmu realizacji pewnej funkcji algorytmy są wymienne Wzorce State i Strategy zostaną omówione wspólnie, ponieważ mają identyczną strukturę i zbliżone cele. Dotyczą one funkcjonalnej zmiany zachowania obiektu w trakcie wykonywania programu. W ten sposób pozornie obiekt ten zmienia klasę, do której należy. W przypadku wzorca State celem jest zmiana zachowania obiektu w zależności od stanu, w jakim obiekt się znajduje. Wzorzec Strategy służy do modelowania algorytmu realizacji pewnej czynności, który może zostać określony i zmieniony w trakcie wykonywania programu. E. Gamma et al. (1995) Wzorce projektowe cz. II

State/Strategy: struktura Bartosz Walter State/Strategy: struktura Stan jest obiektem. Zmiana stanu oznacza zmianę obiektu go reprezentującego. Delegowane do niego metody są wywoływane polimorficznie. W przypadku wzorca State centralnym obiektem jest Context. Jego metody wywoływane przez klientów delegują żądania do skojarzonego z nim relacją kompozycji obiektu typu State, reprezentującego jego stan. Metody obiektu State są polimorficzne, czyli wraz ze zmianą tego obiektu zmienia się też ich funkcjonalność. W ten sposób, gdy zachodzi zmiana skojarzonego z obiektem Context obiektu State, zmieniają się też zachowanie metod kontekstu. Pozornie zatem obiekt Context zmienia klasę, do której należy. Wzorzec Strategy stosuje podobne rozwiązanie, tylko na nieco większą skalę. Obiekt Context realizuje pewien algorytm, którego poszczególne kroki mogą zmieniać się w zależności od wyboru konkretnego algorytmu. Z obiektem tym skojarzony jest (także za pomocą kompozycji) obiekt algorytmu, którego metody implementują zmieniające się kroki. Zmiana obiektu algorytmu powoduje zmianę zachowania obiektu Context. W obu przypadkach najważniejszą zaletą jest możliwość zmiany skojarzonego obiektu (stanu lub algorytmu) w trakcie działania programu, bez potrzeby jego rekompilacji. Wzorce projektowe cz. II

State/Strategy: uczestnicy Bartosz Walter State/Strategy: uczestnicy Context posiada referencję do obiektu reprezentującego bieżący stan State definiuje interfejs pozwalający hermetyzować zachowanie związane z każdym stanem Concrete State definiuje własne metody implementujące zachowanie specyficzne dla tego stanu Obiekt Context posiada referencję do obiektu typu State, wskazującą na bieżący stan. W obiekcie State zdefiniowane są wszystkie metody, których zachowanie zależy od stanu obiektu Context. Wzorce projektowe cz. II

State/Strategy: konsekwencje Bartosz Walter State/Strategy: konsekwencje Podział zachowania obiektu wg stanów kod związany ze jednym stanem jest zapisany w jednym obiekcie zmiana stanu jest realizowana przez zmianę obiektu stanu na inny ochrona przed stanem niespójnym możliwość współdzielenia obiektów State obiekty State zwykle definiują tylko zachowanie obiekty State zwykle są bezstanowe Zastosowanie wzorca pozwala modyfikować zachowanie obiektów tak jakby zmieniała się ich klasa – i to jest najważniejszy cel i konsekwencja tego wzorca. Istnieje natomiast grupa efektów pośrednich, ale o dość interesujących właściwościach. Hermetyzacja stanu w postaci niezależnych klas pozwala na jednorazową, niepodzielną zmianę tego stanu, bez wprowadzania stanów niespójnych czy nieoznaczonych. Jeżeli obiekty State nie przechowują informacji (w większości przypadków może ona być zapamiętana w obiekcie Context, ponieważ ona nie ulega zmianie), a jedynie definiują zachowanie, wówczas – paradoksalnie – obiekty te, reprezentujące stan, są bezstanowe i mogą być współdzielone między wiele obiektów Context. Wzorce projektowe cz. II

State: przykład public class Account { private int balance = 0; Bartosz Walter State: przykład public class Account { private int balance = 0; private String owner = null; private boolean isOpen = false; public Account(String owner, int balance) { this.owner = owner; this.balance = balance; this.isOpen = true; } public void credit(int amount) { if (isOpen) { balance += amount; } else { alert("Konto nieaktywne!"); Przykładem ponownie będzie rachunek bankowy. Tym razem, obok stanu biznesowego, przechowującego informacje związane z rachunkiem (saldo, właściciel), posiada on także zmienną isOpen, określającą, czy rachunek jest aktywny, czy nie. Zmienna ta ma wpływ na działanie niektórych metod biznesowych: wykonanie operacji credit() nie jest możliwe, jeżeli zmienna isOpen ma wartość false. Wzorce projektowe cz. II

State: przykład cd. public interface AccountState { Bartosz Walter State: przykład cd. public interface AccountState { public void credit(Account acc, int amount); } public class AccountOpen implements AccountState { public void credit(Account acc, int amount) { acc.balance += amount; public class AccountClosed implements AccountState { alert("The account is closed!"); Aby zastosować wzorzec State w tym przypadku, należy zdefiniować interfejs AccountState oraz klasy reprezentujące stan aktywności i nieaktywności rachunku. Ten interfejs i implementujące go klasy posiadają metodę credit(), której zachowanie jest różne w zależności od klasy: AccountOpen realizuje tę metodę bezwarunkowo, natomiast AccountClosed – również bezwarunkowo ją blokuje. Wzorce projektowe cz. II

State: przykład cd. public class Account { private int balance = 0; Bartosz Walter State: przykład cd. public class Account { private int balance = 0; private String owner = null; private AccountState state = null; public Account(String owner, int balance) { this.owner = owner; this.balance = balance; this.state = new AccountOpen(); } public void credit(int amount) { this.state.credit(this, amount); // delegacja public void close() { this.state = new AccountClosed(); W klasie Account pole isOpen jest zastąpione poprzez referencję typu AccountState wskazującą na obiekt reprezentujący bieżący stan, przy czym domyślnym stanem początkowym jest stan aktywności (AccountOpen). Metoda credit() w klasie Account jest delegowana do obiektu stanu, dzięki czemu zmiana tego obiektu spowoduje inną obsługę tego komunikatu. Metoda close() powoduje zmianę bieżącego obiektu stanu na AccountClosed – od tego momentu metoda credit() jest zablokowana. Wzorce projektowe cz. II

Bartosz Walter Strategy: przykład Drugi przykład dotyczy wzorca Strategy. Klasa Sorter wykonuje sortowanie wewnętrznej kolekcji. Ponieważ istnieją różne algorytmy sortowania, dlatego realizacja metody sort() jest delegowana do aktywnego algorytmu, stanowiącego implementację klasy SortingStrategy. Metody tej klasy to kroki algorytmu. Każdy algorytm może realizować je w charakterystyczny dla siebie sposób. Zmiana algorytmu sortowania jest realizowana wyłącznie przez zmianę obiektu reprezentującego ten algorytm: jest przezroczysta z punktu widzenia obiektu Sorter. Sorter zleca operację wybranej strategii sortowania. Każda strategia to jeden algorytm. Zmiana strategii nie wpływa na obiekt Sorter. Wzorce projektowe cz. II

Bartosz Walter Decorator: cel Umożliwienie dynamicznego dodawania funkcjonalności do obiektu Stworzenie elastycznej alternatywy dla tworzenia podklas Dekorator jest wzorcem zbliżonym pod względem struktury do wzorców Proxy i Adapter. Celem jego stosowania jest stworzenie możliwości dodawania funkcjonalności do klasy w czasie wykonywania programu. Alternatywnym sposobem realizacji podobnego celu (modyfikacji zachowania wewnątrz grupy klas) jest dziedziczenie, jednak ma ono poważne wady. Jeżeli klasa ma trzy różne właściwości, które mogą wpływać na jej zachowanie i mogą przyjmować wartości binarne (np. klasa Pracownik: wiek – pełnoletni/dziecko, zatrudnienie – pracujący/bezrobotny, stan cywilny – wolny/żonaty), wówczas do reprezentacji wszystkich możliwych przypadków należałoby utworzyć 2^3 = 8 podklas. Liczba ta rośnie wykładniczo wraz ze wzrostem liczby właściwości. Takie rozwiązanie na dłuższą metę jest nieakceptowalne, i dlatego konieczne jest wykorzystanie innego mechanizmu, np. wzorca Decoratora. E. Gamma et al. (1995) Wzorce projektowe cz. III

Bartosz Walter Decorator: struktura Klient wysyła komunikat do obiektu Decorator, który przekazuje go obiektowi ConcreteComponent oraz wykonuje dodatkowe operacje („dekoracje”) Component jest wspólnym interfejsem dla wszystkich klas, które można dekorować. Implementują go zarówno klasa ConcreteComponent, która jest odpowiedzialna za podstawową funkcjonalność oferowaną klientowi, jak i dekoratory. Każdy dekorator posiada referencję (oznaczoną jako kompozycję, aby zaznaczyć obowiązkowość i siłę tej relacji) do innego obiektu Component, którym może być ponownie dekorator lub ConcreteComponent. Otrzymując żądanie wykonania określonej operacji, dekorator deleguje je do swojego „wewnętrznego” obiektu Component, a następnie wykonuje specyficzną dla siebie dodatkową funkcjonalność. Dzięki temu dodanie do obiektu nowej funkcjonalności polega na utworzeniu dekoratora i przekazaniu mu owego obiektu. W ten sposób dekorator staje się rzeczywistym odbiorcą komunikatów od klienta, a ConcreteComponent – jego podwykonawcą. Kiedy każdy dekorator (klasy ConcreteDecoratorA i ConcreteDecorator B) dodaje do dekorowanego obiektu tylko jedną funkcję, wówczas dekorując obiekt wielokrotnie uzyskujemy efekt osiągnięcia żądanej sumarycznej funkcjonalności. Pod względem typu udekorowany obiekt nie różni się od obiektu nieudekorowanego (klient widzi go przez interfejs Component), dlatego zastosowanie tego wzorca nie wymaga istotnych zmian w kodzie klienta. Wzorce projektowe cz. III

Decorator: uczestnicy Bartosz Walter Decorator: uczestnicy Component definiuje wspólny interfejs obiektów, które można dekorować Concrete Component realizuje podstawową funkcjonalność obiektu Decorator posiada referencję typu Component i do tego obiektu deleguje komunikaty rozszerza funkcjonalność obiektu ConcreteComponent Warto zwrócić uwagę, że obiekt ConcreteComponent, aby mógł uczestniczyć w tym wzorcu, musi definiować interfejs Component, którego alternatywną implementacją są dekoratory. Ważne jest też, aby dekoratory odpowiednio delegowały swoje metody do wewnętrznych obiektów typu Component. Wzorce projektowe cz. III

Decorator: konsekwencje Bartosz Walter Decorator: konsekwencje Większa elastyczność w przydziale odpowiedzialności niż w przypadku dziedziczenia Możliwość dodawania funkcjonalności w trakcie wykonywania programu, gdy jest ona potrzebna Tożsamość obiektu z którym komunikuje się klient może się zmieniać wskutek dekoracji Łatwiejsze testowanie poszczególnych dekoratorów Wykorzystanie dekoratorów w celu rozszerzenia funkcjonalności oferowanej przez klasę ma wiele zalet nad stosowaniem dziedziczenia. Przydział odpowiedzialności do obiektu jest dynamiczny i na dowolnym poziomie ziarnistości, zależnym od implementacji dekoratorów. Należy zwrócić uwagę, że zastosowanie dekoratora zmienia referencję do obiektu, do którego odwołuje się klient. Aby uniknąć błędów, warto tworzenie i stosowanie dekoratorów powierzyć specjalizowanej metodzie (typu Factory Method). Ponieważ dekoratory służą do modyfikacji zachowania, a nie przechowywania danych (w szczególności dekoratory mogą być obiektami bezstanowymi), nie należy przechowywać w nich informacji. Pozwala to utrzymać ich relatywnie niewielki rozmiar. Stosowanie dekoratorów przyczynia się do łatwiejszego testowania jednostkowego systemu, ponieważ każdy dekorator wymaga jedynie testów specyficznych dla siebie, a nie dla kompletnie udekorowanego obiektu. Wzorce projektowe cz. III

Bartosz Walter Bridge: cel Oddzielenie interfejsu i implementacji obiektu, tak aby mogły zmieniać się niezależnie od siebie Realizacja funkcji interfejsu niezależnie od możliwości języka programowania Wzorzec Bridge jest niezależnym od języka programowania i oferowanych przez niego możliwości sposobem na rozdzielenie interfejsu i implementacji. W ten sposób oba elementy mogą zmieniać się niezależnie od siebie, tworzyć swoje podklasy etc. Gang of Four Wzorce projektowe cz. III

Bridge: struktura Bartosz Walter Wzorce projektowe cz. III Wzorzec składa się z dwóch interfejsów: Abstraction i Implementor, oraz ich implementacji. Oba interfejsy mogą w rzeczywistości być zwykłymi klasami, jeżeli użyty język programowania nie posiada interfejsów jako swoich elementów. Klient kontaktuje się z obiektem Abstraction i nie jest w żaden sposób zależny od obiektu Implementor. Abstraction jest związany relacją kompozycji z wybranym obiektem Implementor, i do niego deleguje wszystkie żądania przesłane przez klienta. Struktura wzorca bardzo przypomina wzorzec Adapter, jednak cel jest zupełnie inny: intencją jest rozdzielenie abstrakcji od implementacji, tak aby implementacja nie była dostępna dla klienta. Taka struktura pozwala m.in na zmianę obiektu Implementor w trakcie działania programu. Wzorce projektowe cz. III

Bridge: uczestnicy Abstraction Bartosz Walter Bridge: uczestnicy Abstraction definiuje interfejs zewnętrzny (abstrakcję) posiada referencję do obiektu typu Implementor deleguje żądania do obiektu typu Implementor Refined Abstraction rozszerza interfejs Abstraction Implementor definiuje interfejs wewnętrzny (implementację) niespokrewniony z typem Abstraction Concrete Implementor implementuje typ Implementor Z punktu widzenia klienta obiekt Abstraction jest wykonawcą jego poleceń. Aby pełnić swoją rolę, obiekt ten musi posiadać referencję do obiektu Implementor, definiującego interfejs wewnętrzny i faktycznie realizującego wymaganą funkcjonalność. Co ważne, obiekty Abstraction i Implementor nie muszą w żaden sposób (przez dziedziczenie, implementację interfejsu etc.) być ze sobą spokrewnione. Wzorce projektowe cz. III

Bridge: konsekwencje Usunięcie zależności klienta od implementacji Bartosz Walter Bridge: konsekwencje Usunięcie zależności klienta od implementacji Wprowadzenie podziału na niezależne interfejs (Abstraction) i implementację (Implementor) Możliwość niezależnego rozszerzania typów Abstraction i Implementor Wzorzec Bridge, jak nazwa wskazuje (z ang. most), łączy abstrakcję i implementację. Dzięki temu klienci zależą jedynie od abstrakcji i pozostają niezależni od implementacji, która może się zmieniać. Oba „przyczółki mostu” – abstrakcja i implementacja – mogą być rozszerzane przez dziedziczenie niezależnie od siebie. Wzorzec Bridge jest ciekawym sposobem uzupełnienia możliwości oferowanych przez niektóre języki programowania o możliwość tworzenia obiektów o funkcjonalności interfejsu. Wzorce projektowe cz. III

Flyweight: cel Współdzielenie obiektów w celu zwiększenia wydajności Bartosz Walter Flyweight: cel Współdzielenie obiektów w celu zwiększenia wydajności Wydzielenie z obiektu stanu wewnętrznego (współdzielonego) i zewnętrznego (specyficznego) Flyweight jest wzorcem opisującym zasadę współdzielenia obiektów w sytuacjach, gdy są one potrzebne niejednocześnie i tylko przez pewien okres czasu. Flyweight różni się od wzorca Pool of Objects, ponieważ pozwala współdzielić obiekty stanowe, których dane zależą od kontekstu. Wykorzystanie wzorca ma na celu przede wszystkim podniesienie wydajności aplikacji przez ograniczenie liczby obiektów oraz wydzielenie z nich stanu zewnętrznego (specyficznego dla każdej instancji i zależnego od kontekstu) oraz zawarteg w nich tzw. stanu wewnętrznego (współdzielonego przez wszystkie instancje). Na przykład litery w procesorze tekstu są reprezentowane przez obiekty klasy Litera, w której stanem wewnętrznym jest kod znaku, a zewnętrznym – krój litery, jej wielkość, dekoracje etc. E. Gamma et al. (1995) Wzorce projektowe cz. III

Flyweight: struktura Bartosz Walter Wzorce projektowe cz. III Obiektem współdzielonym jest Flyweight, który posiada dwa rodzaje stanu: wewnętrzny, który jest współdzielony przez wszystkie instancje tej klasy, oraz zewnętrzny, który jest specyficzny dla danej instancji. Stan wewnętrzny nie musi być modyfikowany, zatem nie ma konieczności bezpośredniego dostępu do niego. Stan zewnętrzny natomiast musi być dostarczony z zewnątrz w momencie, gdy klient zażąda użycia obiektu Flyweight w konkretnym kontekście. Zarządzaniem obiektami Flyweight zajmuje się obiekt Flyweight Factory, która udostępnia klientowi metodę do pobierania instancji Flyweight. Na podstawie parametrów przekazanych tej metodzie fabryka może ustalić, jaki stan zewnętrzny odpowiada żądanemu obiektowi, i dostarcza go. Fabryka zarządza także pulą generycznych, pozbawionych stanu obiektów Flyweight. W momencie żądania dostarczenia obiektu fabryka pobiera obiekt z puli, konfiguruje go odpowiednim stanem zewnętrznym, i zwraca klientowi gotowy do użycia obiekt. Wzorzec przewiduje też specjalną podklasę UnsharedConcreteFlyweight do reprezentowania tych obiektów, które celowo nie powinny być współdzielone. Jej użycie pozwala na zachowanie struktury wzorca i jego funkcjonalności z punktu widzenia klienta. Wzorce projektowe cz. III

Flyweight: uczestnicy Bartosz Walter Flyweight: uczestnicy Flyweight podlega współdzieleniu między klientów definiuje interfejs do przyjmowania i odtwarzania stanu zewnętrznego obiektu Concrete Flyweight przechowuje stan wewnętrzny (współdzielony) jest niezależny od kontekstu (z wyjątkiem stanu zewnętrznego) Flyweight Factory tworzy i przechowuje obiekty Flyweight Client otrzymuje obiekty Flyweight za pośrednictwem Flyweight Factory Obiekt Flyweight musi posiadać interfejs do obsługi stanu zewnętrznego. Zwykle są to metody dostępowe typu get/set, które konfigurują obiekt. Flyweight Factory stanowi (z punktu widzenia klienta) fabrykę do tworzenia obiektów. Obiekt ten posiada pamięć (pulę obiektów), w której przechowuje wcześniej utworzone instancje. Zajmuje się także zapisem i odtwarzaniem (serializacją i deserializacją) stanu zewnętrznego obiektu. Wzorce projektowe cz. III

Flyweight: konsekwencje Bartosz Walter Flyweight: konsekwencje Zmniejszenie wymagań pamięciowych programu zmniejszenie ogólnej liczby obiektów zmniejszenie rozmiaru stanu obiektów stan zewnętrzny może być przechowywany lub wyliczany Wzrost złożoności obliczeniowej dodatkowy nakład na zarządzanie stanem zewnętrznym Zastosowanie tego wzorca pozwala na znaczne oszczędności pamięci, szczególnie w aplikacjach korzystających z dużej liczby instancji tego samego typu. Z jednej strony ulega zmniejszeniu ogólna liczba utworzonych obiektów, a z drugiej – rozmiar ich stanów wewnętrznych. Oczywiście, konieczne jest także przechowywanie stanu zewnętrznego, jednak w pewnych sytuacjach może on być obliczony, a nie przechowywany, a ponadto nie wymaga on tworzenia i usuwania obiektów, co jest głównym problemem w tego typu aplikacjach. Przykładem zastosowania tego wzorca jest mechanizm zarządzania komponentami EJB w kontenerach. Gdy klient zażąda stworzenia instancji komponentu, kontener pobiera "pustą" instancję z puli i aktywuje ją poprzez wprowadzenie do niej danych, które wynikają z żądania klienta. Po zakończeniu korzystania z instancji przez klienta lub gdy jest ona przez dłuższy czas niewykorzystywana, następuje jej pasywacja, tzn. jej stan zewnętrzny jest z niej usuwany i zapisywany poza nią, a ona sama wraca do puli gotowych obiektów. Ten mechanizm pozwala na znaczną poprawę efektywności kontenera EJB. Wzorce projektowe cz. III

Mediator: cel Uproszczenie komunikacji wielu obiektów Bartosz Walter Mediator: cel Uproszczenie komunikacji wielu obiektów Hermetyzacja mechanizmu wymiany komunikatów Mediator znajduje zastosowanie w sytuacji, gdy wiele obiektów o wspólnym interfejsie musi komunikować się ze sobą w celu wykonania określonego zadania. Najprostszym, lecz trochę naiwnym rozwiązaniem jest powiązanie wszystkich obiektów ze sobą w topologii grafu pełnego. Takie rozwiązanie jest jednak słabo skalowalne: dołączenie kolejnego obiektu powoduje konieczność powiadomienia o zmianie wszystkich pozostałych, aby potrafili skomunikować się z nowym uczestnikiem interakcji. Ponadto powoduje, że mechanizm komunikacji jest rozproszony, co utrudnia jego modyfikację i dalszy rozwój. E. Gamma et al. (1995) Wzorce projektowe cz. III

Bartosz Walter Mediator: struktura Obiekty Colleague i obiekt Mediator tworzą topologię gwiazdy. Komunikaty między obiektami Colleague są przekazywane za pośrednictwem mediatora Wzorzec Mediator proponuje topologię gwiazdy, w której centrum znajduje się właśnie obiekt Mediator. Posiada on referencje do pozostałych obiektów (Colleague) i zna ich zakres odpowiedzialności. Komunikacja pomiędzy obiektami Colleague wymaga pośrednictwa Mediatora, który potrafi przekazać komunikat do właściwego odbiorcy. Wzorce projektowe cz. III

Mediator: uczestnicy Mediator Bartosz Walter Mediator: uczestnicy Mediator definiuje interfejs dołączania i odłączania kolegów Concrete Mediator implementuje mechanizm komunikacji pomiędzy obiektami Colleague posiada referencje do zarejestrowanych obiektów Colleague Colleague definiuje wspólny interfejs dla komunikujących się obiektów posiada referencję do obiektu Mediator komunikuje się z innymi obiektami za pośrednictwem obiektu Mediator Mediator posiada metody służące do dołączania i odłączania obiektów Colleague. Ponadto jego zadaniem jest implementacja mechanizmu komunikacji, czyli podejmowanie decyzji który z obiektów Colleague powinien wykonać określone żądanie. Obiekty Colleague nie są obciążone zadaniem komunikacji z pozostałymi obiektami. Ich wiedza jest ograniczona do znajomości Mediatora. Także dołączenie i odłączenie obiektu Colleague wymaga jedynie powiadomienia Mediatora, a nie wszystkich obiektów. Struktura ta jest przybliżoną analogią do sieci komputerowych, w których komputery znajdujące się w różnych podsieciach komunikują się za pośrednictwem routera. Poszczególne Komputery nie muszą znać adresów wszystkich innych komputerów na świecie, a jedynie adres najbliższego routera. Wzorce projektowe cz. III

Mediator: konsekwencje Bartosz Walter Mediator: konsekwencje Centralizacja mechanizmu komunikacji wyłączna odpowiedzialność obiektu Mediator zmiana mechanizmu wymaga tylko zmiany Mediatora prostota komunikacji vs. złożoność Mediatora Niezależność obiektów Colleague od siebie Uproszczenie protokołów obiektowych Zamiana relacji wiele-wiele na relacje jeden-wiele Mediator narzuca centralizację mechanizmu komunikacji. Odpowiedzialność za komunikację przejmuje w całości Mediator, co z jednej strony pozwala w łatwy sposób modyfikować go lub wymieniać, z drugiej jednak powoduje znaczny wzrost złożoności tego obiektu. Wydaje się jednak, że zamiana taka jest opłacalna, ponieważ uwalnia od problemów związanych z komunikacją resztę obiektów w systemie. Drugą ważną zaletą wzorca jest uniezależnienie obiektów Colleague od siebie: nie posiadają one o sobie żadnej wiedzy, co pozwala modyfikować ich liczbę i funkcjonalność. Wzorce projektowe cz. III

Mediator: przykład public class Mediator { Bartosz Walter Mediator: przykład public class Mediator { private boolean slotFull = false; private int number; public synchronized void storeMessage(int num) { while (slotFull) { try { wait(); } catch (InterruptedException ex ) { } } number = num; slotFull = true; notifyAll(); public synchronized int retrieveMessage() { slotFull = false; return number; Prostym przykładem wzorca Mediator może być znany problem producentów i konsumentów. Producenci nie muszą posiadać jakiejkolwiek wiedzy o konsumentach, ponieważ ich zadaniem jest tylko zapełnianie bufora. Podobnie, konsumenci w żaden sposób nie zależą od producentów, a jedynie od bufora. Bufor pełni rolę mediatora, który koordynuje komunikację między dwoma typami obiektów. Dzięki zastosowaniu wzorca Mediator możliwe jest zwiększanie lub zmniejszanie liczby producentów i konsumentów bez zmiany struktury systemu. Wzorce projektowe cz. III

Mediator: przykład cd. public class Producer extends Thread { Bartosz Walter Mediator: przykład cd. public class Producer extends Thread { private Mediator m; private int no; private static int count = 1; public Producer(Mediator m) { mediator = m; no = count++; } public void run() { int num; while (true) { m.storeMessage(num = (int)(Math.random()*100)); System.out.print("p" + no + "-" + num + " "); Slajd przedstawia kod klasy producenta. Jej logika jest zawarta w metodzie run(), która próbuje wstawić do bufora kolejną liczbę. Wzorce projektowe cz. III

Mediator: przykład cd. public class Consumer extends Thread { Bartosz Walter Mediator: przykład cd. public class Consumer extends Thread { private Mediator m; private int no; private static int count = 1; public Consumer(Mediator m) { count = m; id = count++; } public void run() { while (true) { System.out.print("c" + no + "-" + m.retrieveMessage()); Klasa konsumenta, której kod został przedstawiony na slajdzie, próbuje w nieskończonej pętli odczytać wartość liczby przechowywanej w buforze Mediatora. Wzorce projektowe cz. III

Template Method: cel Stworzenie szkieletu algorytmu w postaci klasy Bartosz Walter Template Method: cel Stworzenie szkieletu algorytmu w postaci klasy Przesunięcie niektórych operacji do podklas Template Method jest prostym wzorcem opisującym sposób współpracy między nadklasą i jej podklasami. Celem zastosowania tego wzorca jest stworzenie szkieletu algorytmu w nadklasie i określenie jego poszczególnych kroków w podklasach. E. Gamma et al. (1995) Wzorce projektowe cz. III

Template Method: struktura Bartosz Walter Template Method: struktura Metody klasy AbstractClass są dziedziczone lub implementowane przez jej podklasy. Metody odwołujące się do metod abstrakcyjnych są ukonkretniane w podklasach. Klasa abstrakcyjna AbstractClass posiada metodę templateMethod() definiującą szkielet algorytmu. Metoda ta odwołuje się do innych metod w tej klasie definiujących podstawowe kroki algorytmu. Część z nich to metody wykorzystywane przez wszystkie podklasy, dlatego są one zdefiniowane w nadklasie i dziedziczone po niej przez podklasy. Ponieważ pozostałe kroki algorytmu mają różną postać w każdym algorytmie, dlatego na poziomie klasy AbstractClass są one deklarowane jako abstrakcyjne. Ich implementacja jest wówczas przesunięta do klas dziedziczących. Wzorce projektowe cz. III

Template Method: uczestnicy Bartosz Walter Template Method: uczestnicy Abstract Class definiuje szkielet algorytmu w postaci metody szkielet odwołuje się do prostych metod abstrakcyjnych Concrete Class implementuje proste metody abstrakcyjne pokrywa inne, wybrane metody odziedziczone z AbstractClass Klasa abstrakcyjna definiuje szkielet algorytmu w postaci metody, która z założenia nie powinna być pokrywana w klasach potomnych i często jest deklarowana jako sfinalizowana. Podklasy muszą dostarczyć implementacji metod abstrakcyjnych nadklasy oraz mogą pokryć niektóre inne odziedziczone metody. Instancja wybranej podklasy dziedziczy zatem szkielet algorytmu i część kroków algorytmu, oraz definiuje samodzielnie pozostałe kroki. Wzorce projektowe cz. III

Template Method: konsekwencje Bartosz Walter Template Method: konsekwencje Odwrócona struktura odwołań zasada hollywoodzka: proszę nie dzwonić, to my oddzwonimy nadklasa odwołuje się do metod w podklasach Analizując konsekwencje zastosowania tego wzorca, warto zauważyć, że taka struktura odwołań jest odwrotna w stosunku do typowej sytuacji, w której podklasa odwołuje się do swojej nadklasy w celu wykorzystania jej funkcji. W tym przypadku to nadklasa odsuwa implementację pewnych kroków algorytmu do podklas. Oczywiście, w celu wykorzystania takiego rozwiązania należy utworzyć obiekt podklasy i wykonać jego metodę-szkielet algorytmu, jednak można tę klasę traktować w sposób abstrakcyjny jako pewną implementację interfejsu nadklasy. Wzorzec Template Method jest powszechnie stosowany w implementacji różnego rodzaju sterowników i wtyczek. za Gang of Four Wzorce projektowe cz. III

Bartosz Walter Iterator: cel Umożliwienie sekwencyjnego dostępu do elementów kolekcji bez ujawniania jej wewnętrznej implementacji Różnorodność kolekcji obiektowych (listy, kolejki, stosy, zbiory, multizbiory, mapy etc.), zarówno funkcjonalna, jak i implementacyjna, powoduje, że wiele z nich wymaga specyficznej obsługi i stosowania zróżnicowanych metod dostępu do elementów. Wzorzec Iterator odpowiada na potrzebę zunifikowanego dostępu do elementów kolekcji, który pozwoli pominąć różnice w ich implementacji. Dzięki niemu, niezależnie od rodzaju kolekcji, jej elementy mogą być przetwarzane sekwencyjnie, z zachowaniem własności poszczególnych kolekcji. E. Gamma et al. (1995) Wzorce projektowe cz. III

Bartosz Walter Iterator: struktura Wzorzec Iterator składa się z dwóch klas abstrakcyjnych: Aggregate i Iterator, oraz dwóch klas konkretnych: ConcreteAggregate i ConcreteIterator. Wszystkie kolekcje są implementacją interfejsu Aggregate, tzn. posiadają metodę tworzącą iterator. Iterator, podobnie jak Aggregate, jest jedynie specyfikacją interfejsu, jaki każdy iterator musi posiadać. Klient, odwołując się do metody createIterator() w kolekcji, otrzymuje klasę implementującą interfejs Iterator. Dzięki temu klient nie zna konkretnej klasy implementacyjnej, a jedynie interfejs, do którego musi się odwoływać. Taka sytuacja ma miejsce np. w bibliotece Java Collections: każda kolekcji tworzy swój własny iterator, który jednak jest dostępny wyłącznie poprzez wspólny interfejs Iterator. W ten sposób mogą one być traktowane w jednolity sposób. Iterator posiada wewnętrzny wskaźnik, który wskazuje na aktualny element kolekcji. Iteratory definiują podstawowe operacje pozwalające na sekwencyjny dostęp do wszystkich elementów dowolnej kolekcji: getFirst() – ustawiająca wskaźnik iteratora na początek kolekcji, getNext() – zwracająca kolejny element, hasNext() – sprawdzająca, czy kolejny element istnieje. W niektórych implementacjach iterator pozwala także na modyfikacje kolekcji, np. dodawanie i usuwanie elementów. Kolekcje o specyficznej strukturze, np. listy mogą udostępniać iteratory wykorzystujące wiedzę o tej strukturze, np. udostępniającą możliwość swobodnego dostępu do elementów kolekcji, zmiany kierunku trawersu kolekcji etc. Z uwagi na konieczność dostępu do elementów kolekcji, iterator musi posiadać prawo odwołania się do nich. W praktyce jest on zatem zwykle klasą zaprzyjaźnioną lub wewnętrzną kolekcji. Każdy Aggregate tworzy specyficzny dla siebie iterator. Klient odwołuje się do niego poprzez abstrakcyjny interfejs. Wzorce projektowe cz. III

Iterator: uczestnicy Aggregate ogólny interfejs każdej kolekcji Bartosz Walter Iterator: uczestnicy Aggregate ogólny interfejs każdej kolekcji deklaruje interfejs do tworzenia iteratora Concrete Aggregate tworzy iterator specyficzny dla własnej struktury Iterator definiuje interfejs sekwencyjnego dostępu do obiektu Aggregate (getFirst(), getNext(), hasNext()) Concrete Iterator implemenetacja interfejsu Iterator specyficzna dla konkretnej kolekcji We wzorcu uczestniczą dwie hierarchie obiektów: związanych z kolekcjami (Aggregate i jej klasy potomne) i związanych z iteracją (Iterator i jego podklasy). Obie hierarchie są powiązane ze sobą wyłącznie poprzez interfejsy. Warto zwrócić uwagę, że struktura wzorca i role pełnione przez poszczególne klasy są szczególnym przypadkiem struktury i ról zdefiniowanych we wzoru Factory Method. Tam również klient odwołuje się do abstrakcyjnej metody klasy-fabryki w celu otrzymania abstrakcyjnego produktu, a faktycznie wywołuje metody w implementacji klasy-fabryki i otrzymuje konkretny produkt zależny od użytej fabryki. Wzorce projektowe cz. III

Iterator: konsekwencje Bartosz Walter Iterator: konsekwencje Abstrakcyjny dostęp do elementów kolekcji Niezależność od implementacji kolekcji Możliwość współistnienia różnych iteratorów w jednej kolekcji Możliwość istnienia wielu iteratorów naraz każdy iterator przechowuje informacje o aktualnym przebiegu iteratory są obiektami stanowymi Iterator pozwala na oddzielenie kolekcji, czyli klasy związanej z przechowywaniem obiektów, od mechanizmu dostępu do tych obiektów. Dzięki temu klient odwołuje się do obiektów w sposób abstrakcyjny, niezależny od konkretnej implementacji kolekcji. Konstrukcja iteratora pozwala na jednoczesne współistnienie wielu niezależnych iteratorów, ponieważ każdy przechowuje wewnętrznie wskaźnik do aktualnie wskazywanego obiektu w kolekcji. Niektóre kolekcje mogą definiować kilka różnych iteratorów, o zróżnicowanej funkcjonalności (w przypadku np. listy) Wzorce projektowe cz. III

Bartosz Walter Visitor: cel Reprezentacja operacji do wykonania na elementach heterogenicznej struktury Realizacja operacji w sposób specyficzny dla typu odwiedzanego elementu Umożliwienie tworzenia nowych operacji bez konieczności modyfikacji klas wewnątrz struktury Wzorzec Visitor jest rozszerzeniem idei stojącej za wzorcem Iterator na kolekcje heterogeniczne, składające się z elementów różnych typów. Jego celem jest dostęp do każdego elementu takiej struktury i wykonanie na nim operacji w sposób dla niego specyficzny. E. Gamma et al. (1995) Wzorce projektowe cz. III

Visitor: struktura Bartosz Walter Wzorce projektowe cz. III Obiekty wchodzące w skład struktury, które obiekt Visitor ma odwiedzić, implementują interfejs Element. Interfejs ten definiuje tylko jedną metodę – accept(), przyjmującą jako jedyny parametr obiekt typu Visitor. Co więcej, metoda ta w każdym przypadku ma identyczną postać: w obiekcie Visitor wywoływana jest metoda visit() z parametrem this, oznaczająca referencję do własnego obiektu. W ten sposób następuje tzw. odwrócenie sterowania: zamieniają się nadawca i odbiorca komunikatu Z drugiej strony we wzorcu uczestniczą obiekty implementujące interfejs Visitor, który definiuje grupę metod visit(), przyjmujących jako parametr obiekty typów, które należy odwiedzić. Każda z tych metod jest zatem przygotowana do odwiedzenia obiektu jednego typu w sposób całkowicie zależny od niego i do niego dostosowany. Jednak konieczne jest automatyczne określenie, która z metod visit() zostanie wykonana. Do tego służy właśnie owo odwrócenie sterowania: przekazywany parametr this jest zawsze typu klasy, w której się znajduje, dzięki czemu metoda zostanie dopasowana automatycznie do typu parametru. Wzorce projektowe cz. III

Visitor: interakcje Bartosz Walter Wzorce projektowe cz. III Obiekt ObjectStructure po kolei wywołuje na każdym ze znajdujących się w nim obiektów Element metodę accept(), przekazując jako parametr obiekt Visitor. Odwiedzany element, jeżeli zgadza się na odwiedziny, wywołuje na obiekcie Visitor metodę visit(), przekazując referencję do samego siebie, reprezentowaną przez this. W ten sposób przekazuje mu swoją zgodę na odwiedziny, a jednocześnie przekazuje referencję do siebie, co umożliwia wykonanie na nim dowolnych publicznych metod (operationA(), operationB()). Wzorce projektowe cz. III

Visitor: uczestnicy Visitor Bartosz Walter Visitor: uczestnicy Visitor definiuje przeciążone metody dla każdego obiektu ConcreteElement do odwiedzenia Element definiuje metodę accept() przyjmującą obiekt Visitor jako parametr Object Structure posiada iterator pozwalający na dostęp do każdego elementu struktury Rolą interfejsu Visitor jest zdefiniowanie przeciążonych metod dla każdego obiektu typu ConcreteElement, który należy odwiedzić. Zatem Visitor jako cały obiekt reprezentuje pewną operację, którą należy wykonać na wszystkich elementach struktury danych, w sposób od nich zależny. Elementy tej struktury posiadają tylko jedną wspólną metodę accept(), która przyjmuje parametr typu Visitor i umożliwia mu (poprzez odwrócenie sterowania) wywołanie metod obiektu Element. Dodatkową zaletą tego rozwiązania jest możliwość zabezpieczenia się niektórych obiektów Element przed odwiedzeniem przez konkretny Visitor, co może mieć znaczenie w niektórych obszarach zastosowań. Obiekt ObjectStructure jest kolekcją, która udostępnia wszystkie swoje elementy i pozwala przekazywać im obiekt Visitor. Wzorce projektowe cz. III

Visitor: konsekwencje Bartosz Walter Visitor: konsekwencje Możliwość odwiedzenia obiektów niespokrewnionych ze sobą Łatwe dodawanie nowych obiektów Visitor Utrudnione dodawanie nowych typów Element konieczność modyfikacji klasy Visitor i jej podklas Możliwość zbierania informacji z elementów struktury w sposób dla nich specyficzny Naruszenie hermetyzacji obiektów Visitor i Element obiekty muszą odwoływać się do swoich publicznych metod Najważniejszą zaletą tego wzorca jest możliwość wykonania przy odwiedzinach każdego elementu w strukturze pewnego kodu zależnego od typu tego elementu. Ta cecha decyduje o popularności i szerokich możliwościach stosowania tego wzorca. Dodawanie nowych implementacji interfejsu Visitor jest łatwe, ponieważ wymaga jedynie stworzenia nowej klasy i zaimplementowania jej metod. Z drugiej strony, dodanie nowego elementu do odwiedzenia jest trudne, ponieważ wymaga dodania nowej metody do wszystkich obiektów Visitor. Wzorzec ten w szczególności nadaje się do akumulacji stanu podczas przejścia przez strukturę obiektów (akumulacja odbywa się wówczas wewnątrz obiektu Visitor). Należy jednak zwrócić uwagę, że wzajemne wywoływanie metod visit() i accept() w obiektach Visitor i Element wymaga, aby metody te były dla siebie wzajemnie dostępne. W C++ można wykorzystać w tym celu klasy zaprzyjaźnione, natomiast w Javie konieczne jest upublicznienie metod, co w pewnym stopniu narusza ich hermetyzację. Wzorce projektowe cz. III

Visitor: przykład public class Bank { Bartosz Walter Visitor: przykład public class Bank { List<BankingProduct> products = new ArrayList<BankingProduct>(); public List<BankingProduct> doReport(Report report) { List<BankingProduct> result = new ArrayList<BankingProduct>(); for (BankingProduct product : products) { result.add(product.accept(report)); } return result; Przykładem zastosowanie wzorca Visitor może być sposób wykonywania raportów bankowych na podstawie wszystkich produktów bankowych, jakie są uruchomione w banku. W banku wykonywane są rozmaite raporty, wymagające inspekcji każdej instancji produktu bankowego, jaka jest prowadzona w banku. Metoda doReport() przyjmuje obiekt raport (czyli właśnie obiekt Visitor) i następnie przekazuje go każdemu produktowi jako parametr metody accept(). Wyniki tej metody (domyślnie – referencja do tego rachunku lub wartość null) jest dołączana do wynikowej listy raportu. Wzorce projektowe cz. III

Visitor: przykład cd. public abstract class BankingProduct { } Bartosz Walter Visitor: przykład cd. public abstract class BankingProduct { } public interface Element { public BankingProduct accept(Report report); public class Account extends BankingProduct implements Element { public BankingProduct accept(Report report) { if (isPriviliged(report)) { return report.visit(this); return null; public class Credit extends BankingProduct implements Element { Dwie klasy reprezentujące produkty bankowe: Account i Credit implementują metodę accept(). W przypadku klasy Account wymagane jest uprawnienie weryfikowane przez metodę isPriviliged(), natomiast w przypadku kredytu weryfikacja ta nie jest przeprowadzana Wzorce projektowe cz. III

Visitor: przykład cd. public class Over1000Report implements Visitor { Bartosz Walter Visitor: przykład cd. public class Over1000Report implements Visitor { public BankingProduct visit(Account acc) { if (acc.balance > 1000) return acc; return null; } public BankingProduct visit(Credit credit) { if (credit.draft > 1000 && credit.isActive()) return credit; public class PassAllReport implements Visitor { public BankingProduct visit(Account acc) { return this; } public BankingProduct visit(Credit credit) { Raport Over1000Report służy do zebrania danych o produktach bankowych o wartości powyżej 1000 PLN. W przypadku odwiedzin obiektu Account wartość 1000 PLN odnosi się do pola balance, natomiast warunkiem odwiedzenia obiektu Credit jest jego aktywacja (metoda isActive()) i wartość pola draft wynosząca powyżej 1000. Drugi raport, PassAllReport, służy do zestawienia wszystkich produktów bankowych bez względu na ich właściwości, dlatego nie dokonuje on żadnej weryfikacji. Wykonanie metody doReport() z obiektem Over1000Report jako parametrem zwróci listę produktów bankowych zawierającą jedynie te z nich, których charakterystyka jest zgodna z odpowiednimi metodami visit() tego raportu, natomiast wykonanie raportu PassAllReport zwróci pełną listę produktów uruchomionych w banku. Wzorce projektowe cz. III

Bartosz Walter Podsumowanie Wzorce projektowe są gotowymi szablonami rozwiązań typowych problemów projektowych Wzorzec posiada określony zestaw atrybutów Katalog wzorców obiektowych stanowi elementarz projektanta oprogramowania Podczas trzech wykładów przedstawiono genezę wzorców projektowych oraz ich typową strukturę. Największą częścią wykładu był przegląd wzorców zaproponowanych przez Bandę Czterech wraz z przykładami. Użycie sprawdzonych rozwiązań, jakimi są wzorce projektowe, pozwala lepiej projektować oprogramowanie obiektowe. Wzorce projektowe cz. III