Wzorce projektowe #2

Transkrypt

Wzorce projektowe #2
Wzorce projektowe #2
Ocena z zajęć
Kod opracowanych przez studenta podlega ocenie.
Termin oddania: 14 dni od terminu zajęć, do godziny 24:00.
Całość powinna zostać umieszczona w SVN każdego studenta:
https://ophelia.cs.put.poznan.pl/svn/io2009/infXXXXX/lab11/DP2/
Wzorce konstrukcyjne
Composite
W celu łatwiejszego zarządzania danymi można je pogrupować. Rozwiązanie to
jest jeszcze efektywniejsze, jeżeli operacje wykonywane na nich są bardzo
zbliżone, bądź identyczne. Dobrze znanym przykładem jest tutaj obsługa
elementów graficznych w programie. Przykładowo rysunek może składać się z
linii, koła, kwadratu i zagnieżdżonego w nim rysunku, w skład którego mogą
wchodzić wymienione elementy. Przykładowa budowa obrazka przedstawiona jest
poniżej:
Aby w łatwy sposób pracować z przedstawionymi obiektami można uporządkować
je w strukturę drzewiastą. Elementem grupującym będzie Rysunek (pełnić one
będą rolą węzła w drzewie), w skład którego wchodzą linie i figury geometryczne
(liście). Wzorzec Composite zakłada identyczną strukturę danych. Dodatkowo
wyróżnia on jeden interfejs, który implementują węzły i liście.
Zastosowanie
●
grupowanie elementów o takiej samej funkcjonalności
●
automatyczne wykonywanie poleceń bez względu z jakim typem danych
mamy o czynienia
Model
Diagram
Uczestnicy
– klient korzystający z drzewiastej struktury danych
●
Client
●
Component – interfejs
węzła (Composite)
●
LeafX
●
Composite
zawierający wszystkie dostępne metody dla liści (Leaf) i
– implementacje elementów końcowych
– implementacja elementu grupującego
Metoda operation() jest abstrakcyjna, gdyż każdy z elementów wykonuje ją
inaczej. Leaf wykonuje konkretne zadanie, natomiast Composite wywołuje polecenia
na elementach wchodzących w jego skład.
Istnieją różne poglądy na temat umieszczania listy wszystkich metod w klasie
Component (np. add i remove), można o nich poczytać w książce Design Patterns.
Implementacja
public abstract class Component {
public abstract void operation();
public void add(Component element) {
...
};
public void remove(Component element) {
...
};
public Component getChild(int id) {
return null;
};
}
public class Leaf extends Component {
private String data;
public Leaf(...) {
...
}
}
@Override
public void operation() {
...
}
public class Composite extends Component {
private ArrayList<Component> elements = new ArrayList<Component>();
public Composite(...) {
...
}
@Override
public void operation() {
for (Component element : elements) {
element.operation();
}
}
public void add(Component element) {
elements.add(element);
};
public void remove(Component element) {
elements.remove(element);
};
public Component getChild(int id) {
return elements.get(id);
};
}
Component node1 = new Composite("node1");
Component leaf1 = new Leaf("leaf1");
Component leaf2 = new Leaf("leaf2");
node1.add(leaf1);
node1.add(leaf2);
node1.operation();
Zadanie
Używając wzorca Composite napisać program odpowiedzialny za przechowywanie
grafiki. Wyjściem programu powinny być nazwy wykonywanych klas, np.:
composite
line
square
circle
composite
line
circle
Proxy
Wiele obiektów jest kosztowna w utworzeniu, np. połączenie do bazy danych,
ściągnięcie danych z sieci komputerowej. Ponadto spora część z tych danych
może nawet nie być potrzebna do pracy, np. użytkownik może z książki
zawierającej 200 stron przeczytać tylko wstęp zawierający 2 strony i zapoznać się
z jednym obrazkiem (pomijając resztę).
W celu zwiększenia efektywności często stosuje się różne rozwiązania. Dzięki nim
można na przykład sprawić, by elementy często wykorzystywane były pobierane
tylko raz i mogły być wielokrotnie używane.
Przykładem takiego rozwiązania jest wzorzec Proxy, który pozwala na stworzenie
dwóch obiektów wyglądających identycznie (implementują one ten sam interfejs),
ale realizujących inne zadania. Pierwszy reprezentuje obiekt rzeczywisty, np.
obrazek. Drugi, zwany obiektem proxy, będzie udawał obiekt docelowy i w
momencie potrzeby wykonania operacji na nim – udostępni go. Oznacza to, że
programista pracując na dowolnym z tych obiektów nie będzie widział żadnych
różnic oraz dopóki obiektu rzeczywisty nie jest potrzebny do pracy, nie będzie on
tworzony/pobierany/itd.
Inne nazwy angielskie
●
Surrogate
Zastosowanie
Wyróżniamy następujące rodzaje proxy:
●
remote proxy – lokalna reprezentacja obiektu występującego w innej
przestrzeni adresowej
●
virtual proxy – tworzenie złożonych struktur na żądanie
●
protection proxy – kontrola dostępu
●
smart reference – warstwa pośrednicząca zapewniająca trwałość obiektu
(persistent) lub inne czynności, np. zliczająca liczbę wywołań
Model
Diagram
Uczestnicy
– interfejs zawierający wspierane metody
●
Subject
●
RealSubject
●
Proxy
●
Client
– implementacja obiektu docelowego
– obiekt reprezentujący element pośredniczący odpowiedzialny za
utworzenie i zarządzanie dostępem do RealSubject
– klient
Implementacja
public abstract class Subject {
public abstract void show();
}
public class RealSubject extends Subject {
public RealSubject(...) {
...
}
}
public void show() {
...
}
public class Proxy extends Subject {
private RealSubject rs = null;
public Proxy(...) {
...
}
public void show() {
if (rs == null) {
rs = new RealSubject(...);
}
rs.show();
}
}
Proxy p = new Proxy(...);
p.show();
Zadanie
Wykorzystując wzorzec Proxy napisać program udostępniający dostęp do obrazka.
Interfejs ma wspierać metody draw, load i save (mają one tylko wyświetlać
komunikat informujący o wykonywanym poleceniu, np. nazwa metody).
Nie przekazywać ścieżki do plik.
Wzorce czynnościowe
State
Praca z danymi i urządzeniami często zależy od stanu w jakim się one znajdują.
Np. aby móc wydrukować dokument, drukarka powinna być włączona i
podłączona do komputera, powinno być odrobinę atramentu/tonera, itd.
Spełnienie wszystkich z wymienionych warunków oznacza, że drukarka jest w
stanie gotowości do pracy.
Rozwiązań programistycznych dla podanej sytuacji jest wiele, jednym z nich jest
wzorzec State. Idea jego polega na stworzeniu obiektu, który wykonanie
konkretnego polecenie deleguje dalej, do obiektu reprezentującego konkretny
stan. Programista zawsze może wykonać polecenie druku, ale tylko gdy
urządzenie jest w odpowiednim stanie zostanie ono pomyślnie wykonane.
Inne nazwy angielskie
●
Objects for States
Nazwy polskie
●
Stan
Zastosowanie
●
Różna zachowanie obiektu w zależności od stanu w którym się znajduje
Model
Diagram
Uczestnicy
– interfejs metod wpieranych przez każdy ze stanów
●
State
●
ConcreteStateX
●
Context
●
Client
– różne implementacje metod w zależności od rodzaju stanu
– klasa zarządzająca, która odpowiada za przekazywanie poleceń do
aktualnego stanu
– klient
Przedstawiony diagram nie pokazuje kilku ważnych zagadnień związanych z
używaniem tego wzorca, a dokładnie: momentu tworzenia stanów,
przechowywanie danych wspólnych dla wszystkich stanów oraz zmiany pomiędzy
stanami.
Tworzenie stanów można rozwiązać na dwa sposoby. Pierwszym z nich jest
tworzenie ich wraz z tworzeniem instancji klasy Context. Drugi to tworzenie
instancji stanów w przypadku gdy są one potrzebne do pracy. Wybór rozwiązana
zależy od ograniczeń z jakim ma się styczność, np. maksymalny czas oczekiwania
na odpowiedź, częstość zmian, wykorzystanie wszystkich/części stanów, itp.
Przechowywanie wspólnych danych zazwyczaj odbywa się w klasie Context, która
przekazywana jest jako parametr do stanów.
Zmiany pomiędzy stanami następują automatycznie i są one wykonywane przez
klasę zarządzającą, bądź przez stan w jakim się aktualnie przebywa.
Implementacja
public abstract class State {
abstract void operation(Context ctx);
}
public class ConcreteState extends State {
void operation(Context ctx) {
...
ctx.setState(ctx.getWorkState());
}
}
public class Context {
private State state;
private State cs;
public Context() {
cs = new ConcreteState();
state = ps;
}
void setState(State state) {
this.state = state;
}
State getConcreteState() {
return cs;
}
}
public void operation() {
state.operation(this);
}
Zadanie
Zrobić szkielet aplikacji do obsługi drukarki przyjmującej stany zgodnie z
poniższym diagramem. Wpierać polecenie start, stop, off, abort.
Pierwsze 3 polecenia powinny być dostępne w trybie czuwania, ostatnie tylko
podczas drukowania.
Proces drukowania rozpoczyna się z chwilą wywołania polecenia start() i trwa aż
programista uruchomi stop().
Mediator
W programach często wykorzystuje się komponenty, które w pewien sposób
oddziałują na siebie. Na przykład w ustawieniach edytora OpenOffice jeśli
wyłączymy wykorzystanie Antyaliasingu (Screen font antyaliasing) opcje zależne
staną się nieaktywne.
Prawidłowe oprogramowanie takich zależności wymaga dużego nakładu pracy.
Stanowi też duże wyzwanie od strony projektowej, by umożliwić łatwy rozwój i
utrzymanie kodu.
Wzorzec Mediator jest propozycją realizacji komponentów wymieniających
informacje między sobą. Zakłada on istnienie zarządcy, który będzie
odpowiedzialny za komunikację pomiędzy komponentami. Będzie on przyjmował
komunikaty od komponentów, analizował je i przekazywał dalej.
Nazwy polskie
●
Mediator
Zastosowanie
●
komunikacja pomiędzy komponentami
●
ponowne wykorzystanie obiektów
●
ograniczenie elementów biorących udział w komunikacji
Model
Diagram
Uczestnicy
Wzorzec ten składa się z klasy, która pełni rolę zarządcy oraz komponentów
(elementów zarządzanych). Każdy z elementów jest tworzony przez zarządcę i
posiada możliwość komunikacji tylko z zarządcą. Komunikacja pomiędzy
elementami jest niedozwolona.
– interfejs zawierający metody dla zarządcy
●
Mediator
●
ConcreteMediator
●
Colleague
●
ConcreteColleague
– zarządca, odpowiedzialny jest za stworzenie
komponentów i koordynację wymiany danych pomiędzy nimi
– interfejs komponentów
– implementacja komponentu
Implementacja
public abstract class Mediator {
public abstract void show();
public abstract void changed(Colleague colleague);
}
public class ConcreteMediator extends Mediator {
@Override
public void changed(Colleague colleague) {
...
}
}
@Override
public void show() {
...
Colleague cc = new ConcreteColleague(this);
cc.operation();
}
public abstract class Colleague {
public abstract void changed();
public abstract void operation();
}
public class ConcreteColleague extends Colleague {
private Mediator mediator;
public ConcreteColleague(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void changed() {
...
mediator.changed(this);
}
@Override
public void operation() {
}
...
changed();
}
Mediator mediator = new ConcreteMediator();
mediator.show();
Zadanie
Wykorzystując wzorzec Mediator zaimplementować logikę stojącą za widgetem
odpowiedzialnym za konfigurację programu.
Stworzyć 2 klasy. Pierwsza odpowiada za włączenie/wyłącznie konfiguracji
(symulacja checkboxa), druga za ustawianie konkretnych pól (wystarczy 1
polecenie operation()).
Jeżeli użytkownik wywoła polecenie wyłączenie konfiguracji, wszelkie zmiany w
drugiej klasie powinny generować komunikat błędu.
Command
Programy użytkowe pozwalają na wywołanie tej samej operacji na wiele
sposobów. Na przykład nowy dokument można utworzyć wybierając opcję w menu
głównym aplikacji, z menu podręcznego albo paska zadań. Czynności tego typu
jest bardzo wiele i umieszczanie tego samego kodu w wielu miejscach jest mało
efektywne. Aby ułatwić zarządzanie takimi poleceniami oraz usprawnić ich
utrzymanie warto pogrupować je i umieścić w jednym obiekcie. Dzięki temu
będzie można go przekazywać w każde miejsce odpowiedzialne za uruchomienie
danego polecenia.
Wzorzec Command pozwala w efektywny sposób zrealizować to zadanie.
Inne nazwy angielskie
●
Action
●
Transaction
Nazwy polskie
●
Polecenie
Zastosowanie
●
jeden mechanizm wykorzystywany w wielu miejscach
●
metody callback
●
wsparcie dla undo
Model
Diagram
Uczestnicy
Wzorzec zakłada stworzenie interfejsu Command, który będzie implementowany
przez każdą klasę odpowiedzialną za jakieś zadanie (ConcreteCommand). Jego
głównym składnikiem jest bezparametrowa metoda execute(), która jest
uruchamiana za każdym razem, np. gdy użytkownik wybierze polecenie w menu.
W związku z brakiem przekazywania jakichkolwiek parametrów, klasa
implementująca musi sama wiedzieć jak ma zareagować.
– interfejs z metodą execute()
●
Command
●
ConcreteCommand
●
Invoker
●
Receiver
●
Client
– implementacja polecenia, np. utworzenie pliku
– wywołuje polecenie execute z ConcretCommand, np. polecenie w menu
- obiekt na którym ConcreteCommand wykonuje operację, referencja do
niego jest przekazywana zazwyczaj przy tworzeniu ConcreteCommand
– tworzy instancje ConcretCommand i przekazuje ją do Invokera (np. do
menu)
Implementacja
public abstract class Command {
public abstract void execute();
}
public class ConcreteCommand extends Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
...
receiver.doSth();
}
}
public class Invoker {
public void setCommand(...) {
...
}
}
public class Receiver {
public void doSth() {
...
}
}
Receiver receiver = new Receiver();
Command cmd = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
invoker.setCommand(CommandType.CC, cmd);
Zadanie
Wywołać polecenie create() odpowiedzialne za utworzenie pliku (czyt.
wyświetlenie komunikatu informującego o utworzeniu pliku) za pomocą wzorca
Command.