Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Wzorce projektowe w Javie. Piotr Pycia Informatyka rok II grupa V Prowadzący zajęcia: mgr.inż..Dominik Radziszowski.

Podobne prezentacje


Prezentacja na temat: "Wzorce projektowe w Javie. Piotr Pycia Informatyka rok II grupa V Prowadzący zajęcia: mgr.inż..Dominik Radziszowski."— Zapis prezentacji:

1 Wzorce projektowe w Javie. Piotr Pycia Informatyka rok II grupa V Prowadzący zajęcia: mgr.inż..Dominik Radziszowski

2 I. Wstęp 1.Co to są wzorce obiektowe? W swoim otoczeniu rozpoznajemy pewne powtarzające się przez cały czas wzorce, z których często się korzysta np.: Ta szafka jest zrobiona w ten sam sposób co tamta. Jedyną różnicą jest to, że po prawej stronie ma szuflady zamiast drzwiczek. Jej przydomowy ogród jest podobny do mojego, z tą różnicą, że w moim rosną róże. Takie same tendencje można zauważyć w programowaniu. Poniżej przytaczamy kilka definicji wzorców projektowych pochodzących z literatury : "Wzorce projektowe stanowią powtarzalne rozwiązanie zagadnień projektowych, z którymi się wciąż spotykamy" [Smalltalk Companion]. " Wzorce projektowe stanowią zbiór reguł określających jak osiągnąć pewne cele w dziedzinie programowania." [Pree, 1994]. "Wzorce projektowe w największym stopniu dotyczą problematyki ponownego użycia powtarzających się motywów architektury programów, zaś szkielety aplikacji dotyczą szczegółów projektowych i implementacyjnych." [Coplien & Schmidt, 1995]. "Wzorzec adresowany jest do powtarzających się problemów, które pojawiają się w specyficznych momentach projektowania i stanowi dla nich rozwiązanie." [Bushmann et al. 1996].

3 "Wzorzec identyfikuje i specyfikuje pewną abstrakcję, której poziom znajduje się powyżej poziomu abstrakcji pojedynczej klasy, instancji lub komponentu." [Gammaetal., 1993]. Wzorce projektowe można traktować jak na wzorce komunikowania się obiektów. Ale nie jest to w 100% prawdą, ponieważ niektóre wzorce, oprócz sposobów komunikacji, określają strategię dziedziczenia i kompozycji, czyli zawierania się w sobie obiektów. Wzorce projektowe mogą dotyczyć wielu poziomów abstrakcji projektowanych systemów, od bardzo niskiego, czyli bardzo specyficznych rozwiązań, do bardzo wysokiego, ogólnego. Wzorce projektowe zostały podzielone na trzy podstawowe grupy wzorców: 1. (ang. creational) konstrukcyjnych – wykorzystuje się je do pozyskania obiektów zamiast bezpośredniego tworzenia instancji klasy. Programy zyskują na elastyczności, gdyż można decydować, jaki typ obiektu ma zostać utworzony w danym przypadku. 2. (ang. structural) strukturalnych – pomagają łączyć obiekty w większe struktury mają zastosowanie np. w implementacji złożonego interfejsu użytkownika. 3. (ang. behavioral) operacyjnych – pomagają zdefiniować komunikację pomiędzy obiektami oraz kontrolować przepływ danych w złożonym programie.

4 II. Wzorce konstrukcyjne Wzorce konstrukcyjne służą do pozyskiwania obiektów. Są ważne, ponieważ to, w jaki sposób tworzone są obiekty, nie powinno mieć dużego wpływu na nasz porgram.

5 1. Fabryka(Factory) Wzorzec ten w zależności od dostarczonych danych, zwraca instancję jednej z możliwych klas. Najczęściej zwracane klasy wywodzą się z tej samej klasy podstawowej i mają takie same metody, ale każda z nich wykonuje swoje zadania w inny sposób i jest zoptymalizowana dla innego rodzaju danych.

6 Przykład zastosowania : Przypuśćmy, że mamy formularz i chcemy umożliwić użytkownikowi wpisywanie swojego imienia i nazwiska w formie "imię nazwisko" lub "nazwisko, imię". Dla uproszczenia przyjęliśmy, że zawsze jesteśmy w stanie określić kolejność dzięki obecności przecinka lub jego braku we wprowadzanym tekście. Podjęcie decyzji jest bardzo proste i może być zrealizowane przez zwykły warunek w pojedynczej klasie. My jednak pokażemy na nim jak działa fabryka i co może wyprodukować. Zaczniemy od zdefiniowania prostej klasy bazowej, która pobiera tekst (klasa String), rozdziela go (w jakiś sposób) na imię i nazwisko. public class Namer { //base class extended by two child classes protected String last ; //split name protected String first; //stored here public String getFirstO { return first; // return first name } public String getLast() { return last ; // return last name }

7 W powyższej klasie nie przetwarza się tekstu, służy ona tylko do przechowywania imienia i nazwiska w osobnych zmiennych, oraz dostarcza metod getFirst, getLast. Aby klasy pochodne miały dostęp do atrybutów tej klasy, zadeklarowaliśmy je jako chronione. Możemy napisać dwie klasy pochodne klasy Namer, których konstruktory będą rozdzielać tekst dostarczony jako argument na dwie części. Przy pisaniu klasy FirstFirst założyliśmy, że wszystko przed pierwszą spacją jest imieniem. public class FirstFirst extends Namer { //extracts first name from last name //when separated by a space public FirstFirst(String s) { int i = s.lastIndexOf(" "); //find sep space if (i>0) { first = s.substring(0, i).trim(); last = s.substring(i + 1).trim(); } else { first = ""; // if no space last = s; //put all in last name }

8 Natomiast pisząc klasę LastFirst założyliśmy, że wszystko przed przecinkiem jest nazwiskiem. Zapewniliśmy również prawidłową obsługę sytuacji, gdy w tekście nie ma ani spacji ani przecinka. public class LastFirst extends Namer { // extracts last name from first name // when separated by a comma public LastFirst(String s) { int i = s.indexOf(","); //find comma if (i>0) { last = s.substring(0, i).trim(); first = s.substring(i+1).trim(); } else { last = s; //if no comma first = ""; // put all in last name }

9 I teraz w klasie NameFactory sprawdzamy, czy w napisie jest przecinek i zwracamy instancję odpowiedniej klasy dla danego przypadku. public class NamerFactory { //Factor decides which class to return based on //presence of a comma public Namer getNamer(String entry){ //coma determines name order int i = entry.indexOf( "," ); if(i > 0) return new LastFirst(entry); else return new FirstFirst(entry); }

10 Rys.1 Diagram(UML) dla fabryki. Klasa X jest klasą podstawową. Klasy XY i XZ są jej klasami pochodnymi. Klasa XFactory podejmuje decyzję, która z klas pochodnych powinna być zwrócona (właściwie instancja jednej z tych klas), w zależności od argumentu dostarczonego metodzie getClass. Nie jest ważne której klasy instancja będzie zwrócona, ponieważ mają one takie same metody, różniące się jedynie implementacją. Decyzja o tym, którą instancję zwrócić, w całości zależy od implementacji klasy XFactory. Może to być bardzo złożona funkcja, ale zwykle jest to prosty warunek.

11 Konsekwencje stosowania wzorca : Tworzymy abstrakcję podejmującą decyzję, którą klasę spośród wielu możliwych klas pochodnych, trzeba zwrócić i zwraca ją. Następnie wywołujemy metody na rzecz tej instancji, nie wiedząc nawet, do której klasy pochodnej odnosi się aktualnie używana instancja. W ten sposób korzystamy z interfejsu klasy, odkładając jego urzeczywistnienie do klas pochodnych. Przy ukryciu konstruktorów mamy lepszą kontrolę nad tworzeniem instancji.

12 2. Singleton Daje pewność, że istnieje tylko jedna instancja klasy i dostarcza globalnego punktu dostępu do tej instancji. W czasie programowania jest wiele przypadków, w których chcemy mieć pewność, że istnieje tylko jeden obiekt klasy. Przykładem jest system, który ma tylko jednego menadżera okien, jedną kolejkę wydruku lub tylko jeden punkt dostępu do systemu baz danych. Komputer ma wiele portów szeregowych, ale tylko jeden jest portem COM1.

13 Pakiet javax.comm jako przykład użycia wzorca Singleton Porty szeregowe są dobrym przykładem zasobów sprzętowych, które powinny być reprezentowane z użyciem wzorca Singleton, ponieważ tylko jeden program w tym samym czasie może korzystać a tego samego portu. Nawet w obrębie jednego programu, tylko jeden jego moduł powinien komunikować się z portem. W pakiecie javax.comm używa się dwóch Singletonów: jeden zarządza kolekcją portów i pozwala utworzyć tylko jeden obiekt dla każdego poru, drugi jest właśnie obiektem, który zarządza pojedynczym portem. Klasa CommIdentifier posiada metodę statyczną, która zwraca obiekt klasy Enumeration. public static Enumeration get.PortIdentifiers() To wyliczenie zawiera obiekty klasy CommPortIdentifer dla każdego portu w systemie. Pakiet obsługuje zarówno porty szeregowe, jak i równoległe. Trzeba przeprowadzić filtrowanie, aby uzyskać tylko porty szeregowe.

14 // get enumeration of ports -static method portEnum = CommPortIdentifier.getPortIdentifiers() ; while (portEnum.hasMoreEtements()) { portId = (CommPortIdentifier)portEnum.nextElement(); if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) ports.addElement (portId.getName( )); // add to list } Statyczna metoda getPortIdentifiers dostarcza pojedynczego globalnego punktu dostępu wymaganego przez wzorzec Singelton. Co więcej, ponieważ jest to metoda statyczna, nie może mieć wielu instancji. Tę metodę można wywołać tylko na rzecz (klasy, a nie jej obiektu. Każda instancja klasy CommPortIdentifier zwrócona przez to wyliczenie ma inną nazwę, którą można odczytać używając metody getName. To wyliczenie zawiera wszystkie możliwe nazwy portów dla danego systemu. Np. w systemie Windows wyliczenie zawiera: COM1, COM2, COM3 i COM4, niezależnie od tego, czy karty tych portów są zainstalowane w komputerze.

15 Jedynym sposobem upewnienia się, czy port jest obecny w systemie, jest próba otwarcia. Jeśli port jest nieobecny, metoda open zgłosi wyjątek NoSuchPortException; jeśli port jest używany przez inny program, zgłosi wyjątek PortInUseException. Oto kod obsługujący powyższe sytuacje: try { // try to get port ownership portId = CommPortIdentifier.getPortIdentifier(portName); // if successful, open the port CommPort cp = portId.open("SimpleComm", 100); // report success status.add( "Port opened: "+portName) ; } catch (NoSuchPortException e) { status.add("No such port: "+portName); } catch (PortInUseException e) { status.add ( portName+" in use by: "+portId.getCurrentOwner()); }

16 Konsekwencje stosowania wzorca : Utworzenie instancji klasy jest możliwe jedynie wtedy, gdy nie utworzono wcześniej żadnej instancji. Można w prosty sposób umożliwić tworzenie większej, ale ograniczonej liczby instancji, jeśli jest to uzasadnione. Pełna kontrola dostępu do instancji Najprostszym sposobem na stworzenie klasy, która może mieć tylko jedną istancję jest umieszczenie w niej statycznej zmiennej, która jest ustawiana podczas tworzenia pierwszej instancji i sprawdzana podczas każdego wywołania konstruktora. Zmienna statyczna występuje tylko w jednej instancji, nie ważne ile obiektów klasy zawierającej tę zmienną zostało utworzonych. Instancja może być utworzona jedynie poprzez wywołanie statycznej metody klasy, która pozwala na utworzenie tylko jednej instancji. Rys.2 Diagram(UML) dla Singletonu.

17 3. Prototyp(Prototype) W tym wzorcu tworzenie obiektu polega na modyfikacji uprzednio utworzonej kopii pewnego obiektu - prototypu. Zastosowanie tego wzorca jest w szczególności uzasadnione, gdy tworzone obiekty zawiera dużą ilość czasochłonnie stworzonych danych, których tylko mała część jest modyfikowana względem prototypu. Tworzeniem prototypu może zarządzać menadżer, który można dostosować tak, aby posiadał np. kilka różnych obiektów i podawał ich kopie w zależności od parametru. Czyli taki menadżer może przyjąć postać Prostej Fabryki. Można wtedy połączyć oba wzorce w ten sposób, iż fabryka przykrywa dostęp do prototypu sama modyfikując kopię obiektu prototypu tworząc żądane instancje. Rys.3 Diagram(UML) dla Prototypu.

18 W wzorcu Prototypu klasa Prototyp jest abstrakcją definiującą operację kopiowania/klonowania. Konkretne klasy potomne implementują metodę kopiowania. Menadżer jest jednocześnie rejestrem prototypów, czyli agreguje ich abstrakcję. W Javie abstrakcją prototypu jest interfejs Cloneable kontraktujący metodę clone, która w klasach implementujących ten interfejs ma za zadanie utworzenie głębokiej kopii obiektu. W przypadku dużych, złożonych klas aby uniknąć żmudnego kodu przepisywania pól dla każdego z podkomponentów, można wykorzystać mechanizm serializacji obiektów, zapisując go do strumienia korzystającego z pamięci (np. ByteArrayOutputStream) i następnie go z niej odzyskując. Trzeba tylko oznaczyć wszystkie klasy jako serializowalne i resztę wykona za nas maszyna. Konsekwencje stosowania wzorca: tworzenie obiektów przez klonowanie obiekty klonowane muszą umożliwiać zmianę swoich właściwości definiujemy nowe rodzaje obiektów bez definiowania nowych typów

19 4.Abstrakcyjna fabryka (Abstract Factory) Wzorca projektowego Abstract Factory można używać w celu otrzymania jednej z wielu związanych ze sobą klas obiektów, z których każdy może na żądanie zwrócić wiele innych obiektów. Wzorzec ten jest fabryką, która zwraca jedną z wielu grup klas. Można nawet gdy używa się Simple Factory, decydować, którą klasę z tej grupy zwrócić. Przykład użycia wzorca Abstrakcyjna fabryka : Stworzymy prostą aplikację, w której wykorzystamy wzorzec Abstract Factory. Napiszemy bardzo prosty program do projektowania ogrodów. Założymy, że mogą być trzy rodzaje ogrodów: ogród jednoroczny, ogród wieloletni i ogród warzywny. Niezależnie od typu ogrodu, musimy zastanowić się nad następującymi kwestiami, związanymi z rozmieszczeniem roślin: Jakie rośliny można posadzić w środku ogrodu? Jakie rośliny będą dobre do obsadzenia obrzeży? Jakie rośliny mogą rosnąć w miejscach zacienionych?

20 Potrzebna nam będzie klasa bazowa: Garden, która potrafiłaby odpowiedzieć na powyższe pytania. public interface Garden { public Plant getShade( ); public Plant getCenter(); public Plant getBorder(); } W naszym wypadku obiekt Plant zawiera tylko nazwę rośliny i umożliwia do niej dostęp. public class Plant { private String name; public Plant(String pname) { name = p name; //save nam } public String getName() { return name ; }

21 W terminologii wzorców projektowych, klasa abstrakcyjna Garden jest Abstract Factory. Deklaruje ona metody konkretnej klasy, które mogą zwracać obiekty jednej z wielu klas; w tym przypadku odpowiedniej rośliny dla centrum, obrzeża i cienia. W prawdziwym systemie należałoby stworzyć olbrzymią bazę danych o roślinach dla każdego typu ogrodu. W naszym przykładzie będziemy mieli tylko jedną roślinę dla każdej kategorii. Na przykład, dla ogrodu warzywnego napiszemy następujący kod: public class VeggieGarden implements Garden { public Plant getShade() { return new Plant("Broccoli"); } public Plant getCenter{) { return new Plant("Corn"); } public Plant getBorder() { return new Plant{"Peas"); }

22 W podobny sposób utworzymy klasy dla ogrodów wieloletniego i jednorocznego. Każda z tych konkretnych klas stanowi fabrykę konkretów, ponieważ implementują one metody zadeklarowane w bazowej klasie abstrakcyjnej. Mamy już kilka obiektów typu Garden, z których każdy może zwracać różne obiekty typu Plant. Zilustruje nam to diagram : Rys.4 Diagram(UML) dla fabryki abstrakcyjnej.(konkretny przykład)

23 Można teraz stworzyć klasę Gardener, która będzie sterowała Abstract Factory tak, aby fabryka zwracała odpowiedni obiekt klasy Garden w zależności od wybranej przez użytkownika opcji. Jedną z głównych zalet Abstract Factory jest to, że można bardzo łatwo dodawać nowe klasy. Np.: jeśli potrzebujemy ogrodu egzotycznego, można stworzyć nową klasę pochodną klasy Garden i produkować jej klasy. Jedyną zmianą, którą należy wprowadzić do kodu jest zapewnienie sposobu wyboru nowego typu ogrodu. Konsekwencje stosowania wzorca: izolacja klas, których obiekty tworzymy łatwa zmiana rodziny tworzonych obiektów - zmiana implementacji abstrakcji fabryki wymusza używanie jednej rodziny obiektów dodawanie nowych rodzin ( jednak pracochłonne )

24 5. Budowniczy(Builder) Ten wzorzec jest podobny do wzorca Abstrakcyjnej Fabryki, gdyż służy on tworzeniu zbioru obiektów, ale tutaj tworzone obiekty są z sobą powiązane (w szczególności gdy tworzą Kompozyt). Obiekt złożony jest tworzony na podstawie danych wejściowych. Dane te są przetwarzane tak, że kolejne ich części tworzą komponenty, które z koleji mogą służyć wraz z innymi danymi utworzeniu obiektu złożonego. W poniższej strukturze zobaczymy iż, budowniczowie stanowią Fabrykę Abstrakcji dla obiektów po prawej stronie. Algorytm ich budowania realizowany jest w klasie dyrektora, która przetwarzając całość danych korzysta przy tym z metod budowniczego. Często w przypadku kompozytu istnieje obiekt nadrzędny, który zawiera wszystkie inne obiekty struktury. Wtedy metoda budowania tego obiektu jest jednocześnie algorytmem budowania całości i dyrektor może zostać "wchłonięty" przez budowniczego.

25 Rys.5 Diagram(UML) dla Budowniczego.

26 Konsekwencje stosowania wzorca: szczegóły budowania obiektu są ukryte poza nim samym budowniczowie są niezależni od innych budowniczych i też częściowo od zachowań tworzonych obiektów zmiana struktury budowanych obiektów może wymagać jednynie zmiany budowniczego abstrakcja procesu tworzenia poza samymi obiektami

27 III. Wzorce strukturalne Wzorce strukturalne opisują sposoby łączenia klas i obiektów w większe struktury.

28 1. Adapter Wzorzec Adapter konwertuje interfejs jednej klasy na interfejs innej klasy. Używamy tego wzorca, jeśli chcemy, żeby dwie niezwiązane ze sobą klasy współpracowały ze sobą w jednym programie. Koncepcja wzorca Adaptera jest bardzo prosta: piszemy klasę, która posiada wymagany interfejs, a następnie zapewniamy jej komunikację z klasą, która ma inny interfejs. Istnieją dwa sposoby realizacji: poprzez dziedziczenie i poprzez kompozycję. W pierwszym wypadku z klasy, która ma niezgodny interfejs wywodzimy klasę pochodną i dopisujemy nowe metody tak, by uzyskać wymagany interfejs. W drugim przypadku zawieramy pierwotną klasę wewnątrz nowej i tworzymy metody wymaganego interfejsu realizujące wywołania metod klasy wewnętrznej.

29 Korzystanie z klasy JList z biblioteki JFC(dla przykładu adaptera) Klasa JList z biblioteki JFC (lubSwing) znacznie różni się od klasy List starszej biblioteki AWT (mają zupełnie inne metody, tworzące dwa różne interfejsy). Wyobraźmy sobie zadanie przepisania starego programu tak, aby korzystał z nowej biblioteki. Naszym zadaniem jest więc emulowanie klasy List z wykorzystaniem JList i adaptera. Możemy zdefiniować potrzebne metody jako interfejs, a następnie stworzyć klasę, która będzie implementowała ten interfejs. public interface awtList{ public void add(String s); public void remove(String s); public String[] getSelectedItems(); } Adapter – wzorzec projektu W rozwiązaniu wzorca obiektu(Rys.), tworzymy klasę, która zawiera w sobie klasę JList i implementuje metody interfejsów awtList. Ponieważ kontenerem zawierającym klasę JList jest klasa JScrollPane, dodaliśmy metody do klasy pochodnej JScrollPane, która emuluje metody klasy List. Metody te są zadeklarowane w interfejsie awtList. Nasza klasa JawtList wygląda następująco :

30 public class JawtList extends JScrollPane implements ListSelectionListener, awtList { private Jlist listWindow; private JListaData listContents; public JawtList(int rows) { listContents = new JListData(); listWindow = new JList(listContents); listWindow.setPrototypeCellValue(Abcdefg Hijkmnop); getViewport().add(listWindow); } public void add(String s) { listContents.addElement(s); } public void remove(String s) { listContents.removeElement(s); } public String[] getSelectedItems() { Object[] obj = listWindwo.getSelectedValues(); String[] s = new String[obj.lenght]; for(int i = 0;i< obj.lenght ; i++) s[i] = obj[i].toString(); return s; }

31 Rys.6 Diagram(UML) dla adaptera – nasz przykład.

32 Zauważmy, że dane są przechowywane w obiekcie klasy JListDane. Klasa ta jest wywiedziona z klasy abstrakcyjnej AbstractListModel, w której zadeklarowane są następujące metody : AddListDataListener (l) - Dodaje obiekt nasłuchujący (listener) zmian danych. RemoveListDataListener(l) - Usuwa obiekt nasłuchujący. FireContentsChanged(obj,min,max) - Nakazuje odświeżenie (ponowne rysowanie) komponentu JList. Wywoływana po każdej zmianie danych pomiędzy indeksami min i max. FireIntervalAdded(obj,min,max) - Nakazuje odświeżenie (ponowne rysowanie) komponentu JList. Wywoływana po każdym dodaniu danych pomiędzy indeksami min i max. FireIntervalRemoved(obj,min,max) - Nakazuje odświeżenie (ponowne rysowanie) komponentu JList. Wywoływana po każdym usunięciu danych pomiędzy indeksami min i max. W naszym przypadku, wszystkim czego potrzebujemy, są metody addElement i removeElement. Za każdym razem, kiedy dodajemy dane do wektora, musimy wywołać metodę fireIntervalAdded, aby został odświeżony odpowiedni fragment wyświetlanej listy. public class JListData extends AbstractListModel { private Vector data; } public JListData() { data = new Vector(); } public int getSize() { return data.size(); }

33 public Object getElementAt(int index) { return data.elementAt(index); } public void addElement(String s) { data.add.Element(s); fireIntervalAdded(this, data.size()–1, data.size() ); } public void removeElement(String s) { data.removeElement(s); fireIntervalRemoved(this, 0, data.size() ); } Adapter – wzorzec klasy Jest on podobny do wzorca obiektu.(Opis dokładniejszy w książce Design Patterns)

34 Podsumowanie : Adapter – wzorzec klasy : Nie nadaje się do emulowania klasy wraz z wszystkimi jej klasami pochodnymi, ponieważ kiedy tworzy się klasę, należy określić, z jakiej klasy będzie dziedziczyć. Emulowane metody nadpisują oryginalne, ale pozostałe są wciąż dostępne w niezmienionej postaci. Adapter – wzorzec obiektu : Mogą być emulowane również klasy pochodne, instancja klasy może być przekazana jako parametr do konstruktora adaptera. Wymaga implementacji wszystkich metod, które mają być emulowane. Adaptery uniwersalne : Jest to koncepcja w której obiekt może być traktowany jako instancja klasy emulowanej lub emulującej, np.: awtList lub Jlist. Adaptery dynamiczne : Może on utworzyć pożądany interfejs dla wielu różnych klas. Może on obsłużyć tylko te klasy które potrafi zidentyfikować. Adaptery w Javie : Pewna ilość adapterów jest wbudowana w język Java. Pozwalają one czsami uprościć niepotrzebnie skomplikowane interfejsy obsługi zdarzeń. Jednym z najczęściej używanych jest klasa WindowAdapter.

35 2. Kompozyt Wzorzec kompozytu pozwala na jednolite traktowanie komponentów i obiektów z nich złożonych poprzez specyfikację ich wspólnego interfejsu. Oprócz operacji budowania obiektu złożonego abstrakcja Kompozytu definiuje również inne operacje. Ich realizacja w Liściu polega na wykonaniu odpowiedniej czynności, która jest iterowana dla wszystkich komponentów obiektu złożonego. Klient dzięki abstrakcji może zapomnieć o strukturze powiązań między obiektami wywołując daną operację. Tutaj obiekt złożony ma strukturę drzewa ale wzorzec można stosować dla dowolnie skomplikowanej struktury.

36 Konsekwencje stosowania wzorca: klient jednolicie wykonuje operacje na obiekcie złożonym i prymitywie łatwo dodawać nowe rodzaje komponentów Rys.7 Diagram(UML) dla kompozytu.

37 3. Dekorator Wzorzec ten pozwala na dekorowanie zachowania klasy, czyli zmianę jej funkcjonalności bez potrzeby dziedziczenia. Dekorator zmienia operacje dowolnego Komponentu przez użycie dodatkowego kodu w stosunku do wywoływanej przy tym operacji obiektu dekorowanego. Wzorca używa się zamiast "naiwnego dziedziczenia". Jeżeli do hierarchi klas musimy dodać nowe podklasy zmieniające zachowanie każdego liścia z tej hierarchi w ten sam sposób lepiej jest stworzyć nową podklasę klasy głównej, która będzie modyfikować zachowanie poprzez delegację. W ten sposób zamiast tworzyć N podklas tworzymy jedną. W Javie np. dekoratorami strumieni są ich filtry.

38 Konsekwencje stosowania wzorca: dynamiczny odpowiednik dziedziczenia - obiekt można dowolnie udekorować podczas wykonania umożliwia rozbicie złożonych funkcjonalności na mniejsze przez systematyczne dekorowanie wprowadza się dużo dodatkowych i "małych" obiektów Rys.8 Diagram(UML) dla dekoratora.

39 4. Waga piórkowa. Waga piórkowa ogranicza ilość tworzonych instancji obiektów, przez przeniesienie części danych z stanu obiektu do parametrów metod co umożliwia ich współdzielenie. Jeżeli część pól, które wymagane są właściwie przez jedną (lub kilka) metod do ich przetwarzania usuniemy z klasy i zażądamy aby dostarczane były one jako parametry wywołania owych metod, to możemy zarządzać ilością dostępnych instancji. Realizuje to menadżer, który podaje wszystkie instancje. Tworzy one nowe jeżeli jest to niezbędne, w przeciwnym razie podaje on pasujący obiekt przechowywany w własnym buforze. Współdzielenie obiektów jest możliwe, gdyż klienci niezbędne dane będą im podawać przy wywoływaniu metod.

40 Rys.9 Diagram(UML) dla wzorca wagi piórkowej. Konsekwencje stosowania wzorca: ograniczenie ilości obiektów zwiększenie kosztu wywołania komunikatu

41 5. Pośrednik. Pośrednik bardzo przypomina dekoratora. Ma on taką samą strukturę, ale jego intencja jest całkiem inna. Obiekt pośrednika ma za zadanie "udawać" obiekt rzeczywisty albo pośredniczyć w komunikacji z nim. Ma to sens, gdy jest to np. obiekt na innej maszynie lub gdy dane obiektu nie są jeszcze dostępne. Struktura jest analogiczna jak w przypadku dekoratora, czyli pośrednik "udaje" obiekt rzeczywisty. Konsekwencje stosowania wzorca: pośrednik ukrywa rzeczywiste położenie obiektu pośrednik może optymalizować dostęp do obiektu Rys.10 Diagram(UML) dla pośrednika.

42 6. Most(Bridge). Wzorzec Bridge wydaje się bardzo podobny do wzorca Adapter. Jest to również klasa, która konwertuje jeden rodzaj interfejsu na inny. Jednak różne były intencje przy opracowywaniu obu wzorców. Przeznaczeniem adaptera jest stworzenie interfejsu dla istniejącej klasy, tak aby wyglądał on jak interfejs innej klasy. Wzorzec Bridge został zaprojektowany, żeby odseparować interfejs klasy od jego implementacji. Dzięki temu można zmieniać implementację bez potrzeby wprowadzania zmian w kodzie, który korzysta z klasy. Uczestnikami wzorca Bridge są: abstrakcja, która definiuje interfejs klasy mostu, implementator, który definiuje interfejs klasy implementującej konkretne implementatory, którymi są klasy implementujące abstrakcja pierwotna, z której korzystają (poprzez dziedziczenie lub kompozycję) klasy implementujące.

43 Przykład zastosowania wzorca mostu. Przypuśćmy, że mamy program, który wyświetla w oknie listę produktów. Najprostszym interfejsem do wyświetlania jest klasa JList. Kiedy zostanie sprzedana znacząca liczba produktów, chcemy, żeby w tabeli obok produktów były wyświetlane wartości sprzedaży. Załóżmy, że musimy zapewnić dwa sposoby wyświetlania danych o produktach: widok dla pracownika, w którym będzie jedynie lista produktów i widok dla kierownika, w którym dla każdego produktu będzie wyświetlana liczba sprzedanych sztuk. Listę produktów będziemy wyświetlać za pomocą klasy JList, a widok dla kierownika za pomocą klasy JTable. Te dwa rodzaje wyświetlania stanowić będą implementację klas wyświetlających. Teraz chcielibyśmy zdefiniować jeden prosty interfejs, który pozostawałby niezmieniony niezależnie od rodzaju i skomplikowania klas implementujących. Zacznijmy od zdefiniowania abstrakcyjnej klasy Bridger. public abstract class Bridger extends JPanel { public abstract void addData(Vector v); }

44 Ta klasa jest bardzo prosta, przejmuje wektor zdanymi i przekazuje go do klasy wyświetlającej. Z drugiej strony mostu znajdują się klasy implementujące, które zazwyczaj mają bardziej rozbudowany i bardziej niskopoziomowy interfejs. Będziemy im przekazywać pojedyncze linie danych do wyświetlenia. public interface visList { public void addLine(String s); public void removeLine(int num); } W tej definicji mechanizm odróżniania tej części napisu, w której przechowywana jest nazwa produktu, od części, w której jest przechowywana liczba sprzedanych sztuk pozostaje niejawny. W tym prostym przykładzie liczba sprzedanych sztuk będzie oddzielana od nazwy za pomocą dwóch myślników. Mostem pomiędzy interfejsem po lewej i implementacją po prawej jest klasa listBridge, która tworzy instancje jednej z klas wyświetlających listę. Można zauważyć, że rozszerza ona klasę Bridger.

45 // this is the Irnplementor class // that decides which class to return public class listBridge extends Bridger { protected visList list; public listBridge(visList jlist) { setLayout(new BorderLayout()); list = jlist; add("Center", (JComponent)list); } public void addData(Vector v) { for (int i = 0; i < v.size(); i++) { String s = ( String )v.elementAt (i); list.addLine (s); }

46 Następnie na najwyższym poziomie programu po prostu tworzymy instancje tabeli, lub listy korzystając z klasy listBridge. // add in customer view as list box listBridge lList = new listBridge(new productList()) lList.addData (prod); pleft.add("North", new JLabel("Customer view")); pleft.add("Center", lList); // add inexecutive view as table listBridge lTable = new listBridge(new productTable()); lTable.addData (prod); pright.add("North", new JLabel("Customer view")); pright.add("Center", lTable); Diagram klas(następny slajd) przedstawia odseparowanie interfejsu od implementacji. Klasa Bridger jest abstrakcją, klasa listBridge jest implementacją tej abstrakcji. Interfejs visList definiuje interfejs implementatora. Klasy productList i productTable są konkretnymi implementatorami.

47 Rys.11 Diagram(UML) dla mostu – nasz przykład. Oba konkretne implementatory bardzo różnią się od siebie, jednak oba stanowią implementację interfejsu visList.

48 Konsekwencje stosowania wzorca : wzorzec ten pozwala zachować stały interfejs dla programu klienta podczas wprowadzania dowolnych zmian do wykorzystanych klas. Pozwala to uniknąć potrzeby rekompilowania modułów klienta, a wymaga tylko rekompilacji samego mostu i klas implementujących. można niezależnie rozszerzać klasy implementujące i klasę mostu. o wiele łatwiej można ukryć szczegóły implementacyjne przed programem klienta.

49 7. Fasada Często program podczas tworzenia ewoluuje i rośnie stopień jego komplikacji. Możemy zauważyć że oprócz korzyści wzorce mają też ujemną cechę: czasami generują one bardzo wiele dodatkowych klas, przez co trudniej jest zrozumieć działanie programu. Poza tym programy często składają się z szeregu podsystemów, z których każdy posiada swój własny skomplikowany interfejs. Fasada pozwala uprościć tę złożoność dostarczając uproszczonego interfejsu do podsystemów. Takie uproszczenie może czasami zmniejszyć elastyczność przykrytych klas, lecz często dostarcza wszystkich funkcji niezbędnych każdemu użytkownikowi. Oczywiście przykrywane klasy i ich metody mogą być w dalszym ciągu dostępne. Klasy obsługujące bazy danych z pakietu java.sql stanowią doskonały przykład niskopoziomowych klas, które komunikują się ze sobą w bardzo zawiły sposób.

50 Przyjrzyjmy się, jak nawiązuje się połączenie z bazą danych. Najpierw trzeba załadować sterownik bazy danych. try { Class.forName(driver); // load the Bridge driver }catch (Exception e) { System.out.println(e.getMessage()); } Następnie używamy klasy Connection do łączenia się z bazą danych. Pobieramy również metadane, aby dowiedzieć się więcej o bazie danych. try { con = DriverManager.getConnection(url); dma = con.getMetaData(); // get the meta data } catch (Exception e) { System.out.println(e.getMessage()); } Jeśli chcemy sporządzić listę wszystkich nazw tabel bazy danych, musimy wywołać metodę getTables klasy Metadata, która zwróci obiekt ResultSet. Aby uzyskać listę nazw musimy przejść przez wszystkie elementy tego obiektu, i wyłuskać tylko tabele użytkownika, odrzucając tabele systemowe.

51 Vector tname = new Vector( ) ; // add the table names to a Vector // since we don't know how many there are try { results = new Results(dma.getTables(catalog, null, "%", types)); } catch (Exception e) { System.out.println(e); } while (results.hasMoreElements()) tname.addElement(results.getColumnValue("TABLE_NAME)); Jak widać, już teraz bardzo trudno jest tym wszystkim zarządzać, a nie wykonaliśmy nawet jeszcze żadnego zapytania. I Możemy przyjąć pewne założenie upraszczające: wyjątki, które są zgłaszane przez metody klas nie wymagają skomplikowanej obsługi. W przeważającej części metody będą pracowały bezbłędnie, dopóki prawidłowo będzie działać połączenie sieciowe z serwerem bazy danych. Więc możemy obudować wszystkie te metody, tak by błędy, które wystąpią były wypisywane bez podejmowania żadnych dodatkowych akcji. Możliwe jest teraz napisanie prostych klas zawierających wszystkie ważne metody klas Connection, ResultSet, Statement i MetaData. Tak będą wyglądały metody klasy Database:

52 class Database { public Database(String driver); // constructor public void Open(String url, String cat); public String[]getTableNames(): public String[]getColumnNames(String table); public String getColumnValue(String table, String columnName); publicString getNextValue(String columnName); public ResultSetExecute(String sql); } A tak, metody klasy Results. class Results { public Results(ResultSet rset); // constructor public String[]getMetaData(); public booleanhasMoreElements(); public String[]nextElement(); public StringgetColumnValue(String columnName); public StringgetColumnValue(int i) } Te proste klasy pozwalają nam napisać program otwierający połączenie z bazą danych i wyświetlający nazwy jej tabel, kolumn i zawartość. Program umożliwia również wykonanie prostego zapytania SQL.

53 Nasz przykładowy program wykorzystujący fasadę daje dostęp do bazy danych zawierającej ceny żywności w trzech okolicznych supermarketach. Po uruchomieniu program łączy się z bazą danych i pobiera listę tabel. db = new Database("sun.jdbc.odbc.JdbcOdbcDriver"); db.Open("jdbc:odbc:Grocery prices", null); String tnames[] = db.getTableNames() ; loadList(Tables, tnames); Kliknięcie w obszarze listy powoduje wykonanie zapytania dla nazw kolumn lub zawartości. public void itemstateChanged(ItemEvent e) { Object obj = e.getSource(); if (obj == Tables) showColumns(); if (obj == Columns) showData(); } private void showColumns() { String cnames[] = db.getColumnNames(Tables.getSelectedItem(); loadList(Columns, cnames); }

54 private void showData() { String colname = Columns.getSelectedItem(); String colval = db.getColumnValue(Tables.getSelectedItem(), colname); Data.setVisible(false); Data.removeAll(); Data.setVisible(true); colval = db.get.NextValue(Columns.getSelectedItem()); while (colval.lenght() > 0) { Data.add(colval); wartosc = db.get.NextValue(Columns.getSelectedItem()); } Klasa Database zawiera instancję klasy Connection, DataBaseMetaData i Results. Z kolei Results zawiera instancje klas ResultSet i ResultSetMetadata. Możemy to zobaczyć na diagramie:

55 Rys.12 Diagram(UML) dla fasady – nasz przykład. Konsekwencje stosowania wzorca : wzorzec ten izoluje klienta od skomplikowanych komponentów podsystemów i dostarcza do nich prostszy interfejs do ogólnego użytku. Jednak nie ogranicza zawansowanemu użytkownikowi dostępu do złożonych klas znajdujących się głębiej. Umożliwia także dokonywanie zmian w przykrych podsystemach bez potrzeby modyfikacji kodu klienta i redukuje liczb zależności podczas kompilacji.

56 IV. Wzorce operacyjne Wzorce operacyjne są wzorcami komunikowania się obiektów między sobą.

57 1. Iterator Wzorzec Iteratora jest jednym z prostszych i najczęściej wykorzystywanych. Pozwala przemieszczać się poprzez listę lub dowolną kolejkę danych, z wykorzystaniem standardowego interfejsu, bez potrzeby znajomości wewnętrznej reprezentacji danych. Można też zdefiniować specjalne iteratory, które dokonują specjalnego przetwarzania i zwracają tylko niektóre elementy kolekcji danych. Wzorzec Iteratora jest użyteczny, ponieważ stanowi dobrze zdefiniowany sposób przemieszczania się poprzez zbiór elementów, bez pokazywania tego, co dzieje się wewnątrz klasy. Iterator jest interfejsem, więc można go zaimplementować w jakikolwiek sposób, byle był najwygodniejszy dla zwracanych danych. Wzorzec sugeruje, że dobry interfejs iteratora powinien mieć następujące metody: public interface Iterator { public Object First(}; public ObjecNext(); public boolean isDone(); public Object Currentltem{); }

58 Gdy stosuje się ten interfejs, można zacząć od początku listy, przemieszczać się poprzez kolejne elementy listy, sprawdzić, czy lista ma jeszcze więcej elementów oraz pobrać bieżący element listy.Interfejs jest prosty do zaimplementowania i posiada szereg zalet. Jednak w Javie powszechnie stosuje się inny Iterator : public interface Enumeration { public boolean hasMoreElements(); public Object nextElement(); } Brak metody do przemieszczania się na początek listy może na pierwszy rzut oka wyglądać na duże ograniczenie. Jednak nie jest to problemem, ponieważ zwykle pobieramy nową instancję wyliczenia klasy implementującej interfejs Enumeration, za każdym razem, kiedy chcemy przemieszczać się poprzez listę. Przykładem do pokazania działania wzorca Iteratora będzie lista zawodników wraz z klubem i czasem. Utworzymy klasę KidData która jest w istocie klekcją obiektów zawodników posiadających atrybuty : nazwisko, klub i czas, przechowywaną w wektorze.

59 public class KidData { private Vector kids; private Hashtable clubs; public KidData(String filename) { kids = new Vector(); clubs = new Hashtable(); InputFile f = new InputFile(filename); String s = f.readLine(); while(s!= null) { if(s.trim().lenght() > 0){ Kid k = new Kid(s); kids.addElement(k); clubs.put (k.getClub, k.getClub() ); } s = f.readLine(); } Aby wyliczyć wszystkich zawodników kolekcji, pobieramy wyliczenie z samego wektora : public Enumeration elements() { return kids.elements() }

60 Posiadanie jasno zdefiniowanych metod przemieszczania się poprzez kolekcję danych jest bardzo pomocne. Można również zdefiniować filtrowane wyliczenia, które wykonują przetwarzanie danych zanim je zwrócą. Na przykład można zwrócić dane uporządkowane w jakiś szczególny sposób lub zwrócić jedynie te obiekty, które spełniają pewne kryteria. Zamiast tworzenia wielu podobnych interfejsów dla tych filtrowanych wyliczeń, dostarczamy metody, które zwracają każdy z typów wyliczeń mających takie same interfejsy. W diagramie zostało zaznaczone filtrowanie do przykładu w którym potrzebujemy uzyskać zawodników grających w jednym klubie. Cała praca jest wykonywana w metodzie hasMoreElements. Metoda ta skanuje kolekcję danych szukając następnego zawodnika spełniającego warunek przynależności do klubu, którego nazwa została wyspecyfikowana w konstruktorze. Następnie w zmiennej kid jest albo zapisywany obiekt zawodnika, albo jest ona ustawiana na wartość null i zwracana jest stosowna wartość true lub false. Metoda nextElement zwraca albo zawodnika ze zmiennej kid, albo zgłasza wyjątek, jeśli nie ma więcej zawodników. Zauważmy, że w warunkach normalnego użytkowania wyjątek nie powinien być nigdy zgłoszony, ponieważ metoda hasMoreElements wcześniej informuje o tym, że nie ma już więcej zawodników.

61 Rys.13 Diagram(UML) dla iteratora - nasz przykład. Konsekwencje stosowania wzorca : Zmienianie się danych: najważniejsza kwestia dotycząca używania iteratorów dotyczy przemieszczania się poprzez kolekcję danych, które są w tym samym czasie modyfikowane. Jeśli kod jest obszerny i tylko co pewien dłuższy czas przechodzi do następnego elementu, mogło się zdarzyć, że element w tym czasie został dodany lub usunięty. Jest również możliwe, że inny wątek mógł zmienić coś w kolekcji danych. Nie ma prostych rozwiązań tego problemu.

62 Można uczynić iterator bezpiecznym ze względu na wątki poprzez wprowadzenie synchronizacji. Jeśli jednak chcemy przemieszczać się poprzez dane w pętli używając iteratora i usuwać niektóre elementy, musimy bardzo uważać na konsekwencje. Usunięcie lub dodanie elementu może oznaczać, że element ten zostanie opuszczony lub odwiedzony dwukrotnie, w zależności od zastosowanego mechanizmu przechowywania danych. Dostęp do danych: klasa iteratora musi mieć zapewniony dostęp do struktury danych zawartej w klasie, dla której tworzymy iterator. Jeśli dane są przechowywane w takiej klasie, jak Vector lub HashTable, to zapewnienie dostępu do danych jest bardzo łatwe. Jednak jeśli jest to jakaś inna struktura danych zawarta w klasie, prawdopodobnie jedynym wyjściem będzie dodanie do tej klasy metody get, poprzez którą zostanie udostępniona struktura danych. Inną możliwością jest utworzenie iteratora jako klasy pochodnej klasy zawierającej strukturę danych i bezpośredni dostęp do danych. Rozwiązanie w postaci klas zaprzyjaźnionych (friend) niestety nie ma zastosowania w Javie. Iteratory wewnętrzne i zewnętrzne: opisane zostały tylko iteratory zewnętrzne. Iteratory wewnętrzne są to metody, które przemieszczając się poprzez całą kolekcję danych, wykonują pewne operacje bezpośrednio na każdym elemencie, bez jakiegokolwiek specyficznego żądania ze strony użytkownika. Iteratory wewnętrzne są rzadziej spotykane w Javie, jednak spotyka się metody, które normalizują kolekcję danych tak, aby elementy

63 2. Łańcuch odpowiedzialności(Chain of Responsibility) Wzorzec Chain of Responsibility umożliwia pewnej liczbie klas podjęcie próby służenia żądania, podczas gdy żadna z nich nic nie wie o możliwościach innych klas. Pozwala to zrezygnować z powiązań pomiędzy tymi klasami. Jedynym wspólnym powiązaniem jest przekazywane pomiędzy nimi żądanie. Żądanie jest przekazywane aż do czasu, kiedy klasa która je otrzyma, będzie potrafiła to żądanie obsłużyć.

64 Diagram(UML) dla przykładu systemu pomocy.(Disign Patterns) Konsekwencje stosowania wzorca : Najważniejszym celem tego wzorca, podobnie jak wielu innych wzorców, jest zredukowanie połączeń pomiędzy obiektami. Jedyne, co muszą wiedzieć poszczególne obiekty to to, jak przekazać żądanie dalej do innych obiektów. Każdy obiekt łańcucha jest niezależny. Nie posiada żadnej informacji o innych obiektach, musi jedynie zadecydować, czy może obsłużyć odebrane żądanie. Sprawia to, że napisanie każdego z nich, jak i skonstruowanie łańcucha, jest łatwe. Można zadecydować, czy ostatni obiekt łańcucha będzie obsługiwał wszystkie żądania, czy też niektóre żądania pozostaną nie obsłużone. Jednak trzeba wiedzieć który obiekt będzie ostatnim w łańcuchu. Ponieważ Java nie zezwala na wielokrotne dziedziczenie, klasa bazowa łańcucha powinna być interfejsem, a nie klasą abstrakcyjną. Dzięki temu klasy pochodne mogą dziedziczyć z innej użytecznej hierarchii(np.: kompozytów wirtualnych). Wadą takiego rozwiązania jest to, że często musimy oddzielnie w każdym module implementować łączenie łańcucha, wysyłanie i przekazywanie żądań.

65 3. Mediator Kiedy program składa się z wielu oddzielnych klas, logika i przetwarzanie są rozdzielone pomiędzy te klasy. Wraz ze wzrostem liczby oddzielnych klas w programie, komunikacja pomiędzy nimi staje się coraz bardziej złożonym zagadnieniem. Duża liczba powiązań pomiędzy klasami powoduje, że każda klasa musi zawierać informacje o metodach innych klas oraz że program staje się mało czytelny i trudny w utrzymaniu. Wprowadzanie zmian może stać się bardzo trudne, ponieważ każda zmiana może powodować konieczność zmieniania kodu w wielu klasach programu. Wzorzec Mediatora pozwala rozwiązać ten problem poprzez rozluźnienie powiązań pomiędzy klasami. Mediator stanowi jedyną klasę, w której zawarte są informacje o metodach innych klas. Klasy informują Mediatora o wystąpieniu zmian, a Mediator informuje inne klasy, które powinny być o tym powiadomione.

66 Konsekwencje stosowania wzorca : Wzorzec zapobiega skomplikowanym powiązaniom pomiędzy klasami, kiedy operacja wykonywana w jednej klasie musi zmienić stan obiektu innej klasy. Wzorzec Mediatora pozwala łatwo modyfikować działanie programu.W wielu wypadkach wystarczy zmienić implementację klasy mediatora lub utworzyć jego klasę pochodną. Można dodawać nowe komponenty wizualne bez potrzeby zmieniania czegokolwiek poza Mediatorem. Mediator rozwiązuje problem obiektów poleceń. Nie muszą one już posiadać informacji o pozostałych komponentach interfejsu użytkownika. Ponieważ Mediator staje się "klasą bogiem", czasem przechowuje zbyt wiele informacji o reszcie programu. Może to sprawić, że mediator będzie klasą trudną w utrzymaniu i trudno będzie wprowadzać do niej modyfikacje. Można poprawić sytuację poprzez umieszczenie większej liczby funkcji w osobnych klasach, zamiast w Mediatorze. Każdy obiekt powinien wykonywać swoje własne zadania, a Mediator powinien kontrolować tylko interakcje pomiędzy obiektami. Każdy Mediator jest samodzielnie pisaną klasą, która musi znać i wywoływać metody wszystkich kompanów. Sprawia to, że nie jest to klasa nadająca się do ponownego użycia w innych projektach. Jednak Mediator jest dosyć prosty i napisanie jego kodu jest o wiele łatwiejsze niż zarządzanie skomplikowanymi interakcjami obiektów w jakikolwiek inny sposób.

67 4. Stan(State) Wzorzec State pozwala utworzyć obiekt, który reprezentuje stan aplikacji i zmieniać stan aplikacji poprzez przełączanie stanu tego obiektu. Na przykład możemy utworzyć klasę-kontener, za pomocą której możemy się przełączać pomiędzy kilkoma zawartymi w niej klasami i przekazywać wywołania metod do klasy aktywnej w tym momencie. Wielu programistów ma doświadczenia z tworzeniem klas, które wykonują niewiele różniące się od siebie przetwarzanie lub wyświetlają różne informacje w zależności od argumentu dostarczonego do klasy. Prowadzi to często do konieczności stosowania instrukcji warunkowych lub switch wewnątrz klasy, których zadaniem jest określenie, jak powinien zachować się obiekt. Wzorzec State próbuje wyeliminować takie nieeleganckie rozwiązania.

68 Konsekwencje stosowania wzorca : Wzorzec State tworzy obiekty klas pochodnych bazowej klasy State dla każdego stanu, w którym może znaleźć się aplikacja i przełącza pomiędzy nimi, gdy zmieni się stan aplikacji. Nie ma potrzeby tworzenia zestawu długich instrukcji warunku lub instrukcji switch testujących zmienne związane z różnymi stanami, ponieważ każdy stan jest ukryty wewnątrz odpowiedniej klasy. Nie trzeba za każdym razem sprawdzać zmiennej określającej stan, co eliminuje błędy spowodowane przez programistę, który zapomniał sprawdzić aktualny stan. Można współdzielić obiekty stanu pomiędzy różnymi częściami aplikacji, takimi jak oddzielne okna, pod warunkiem, że obiekty stanu nie będą miały specyficznych zmiennych instancji. Takie rozwiązanie generuje wiele małych klas, ale upraszcza program i powoduje, że jest on bardziej zrozumiały

69 Bibliografia : Java. Wzorce projektowe - J.W.Cooper Zasoby internetowe Reszta spotykanych wzorców projektowych jest omówiona w podanej literaturze.


Pobierz ppt "Wzorce projektowe w Javie. Piotr Pycia Informatyka rok II grupa V Prowadzący zajęcia: mgr.inż..Dominik Radziszowski."

Podobne prezentacje


Reklamy Google