Wzorce projektowe - Observer

Posted on May 22, 2008, under wzorce projektowe.

No i kolejny użyteczny wzorzec – Observer - korzysta się z niego bardzo często, bo na przykład jest o niego oparta cała obsługa zdarzeń w javowym Swingu.

Cel wzorca:

Zdefiniowanie zależności jeden do wielu pomiędzy obiektami, takiej że gdy jeden obiekt zmienia swój stan wszystkie z nim powiązane są o tym poinformowane i automatycznie uaktualnione.

Używamy go gdy nie chcemy tworzyć skomplikowanych klas, o wielu zadaniach, które zmniejszyły by naszą elastyczność w ich używaniu (co jest niezgodne z założeniami projektowania programów zorientowanych obiektowo). Głównymi pojęciami w tym wzorcu jest podmiot (subject) oraz obserwator (observer). Każdy podmiot, może posiadać dowolną ilość obserwatorów. Wszystkie one są informowane o zmianie stanu podmiotu.

Stosowany gdy:

  • gdy dwa (przynajmniej) obiekty zależą od siebie
  • kiedy zmiana jednego obiektu powinna nieść z sobą zmianę innego

Struktura:

Podmiot:

  • zna swoich obserwatorów, dowolna ilość obserwatorów może obserwować dany podmiot
  • dostarcza interfejsu do dodawania i usuwania obserwatorów.

Obserwator:

  • dostarcza interfejsu dla obiektów które powinny zostać poinformowane o zmianie stanu podmiotu

KonkretnyPodmiot:

  • posiada stan który interesuje obserwatorów
  • wysyła do obserwatorów informację o zmianie swojego stanu

KonkretnyObserwator:

  • posiada referencję do podmiotu którego stan monitoruje
  • przechowywuje stan który powinien być spójny ze stanem podmiotu
  • implementuje interfejs dostarczony przez Obserwatora – aby móc zostać poinformowanym o zmianie stanu podmiotu

Konsekwencje stosowania wzorca:

  • podmiot nie zna konkretnej klasy obserwatorów, interesuje go tylko to iż implementują one odpowiedni interfejs i musi ich poinformować o swojej zmianie stanu
  • wspiera komunikację typu broadcast

Implementacja w javie:

Java w prosty sposób wspiera implementację tego wzorca projektowego. Pakiet java.util zawiera:

interfejs Observer – który ułatwia tworzenie obserwatorów – zawiera on tylko jedną metodę, która jest wywoływana za każdym razem gdy zmieni się stan obiektu:

void update(Observable o, Object arg)

gdzie o jest podmiotem którego obserwujemy, a arg jest argumentem który został przekazany do metody notifyObservers przez podmiot.

klasę Observable -która reprezentuje podmiot, którego zmiany stanu nas interesują. Klasa jest dziedziczona przez podmiot. Zgodnie z założeniami wzorca, podmiot może mieć wielu obserwatorów. Po zmianie stanu podmiotu wywoływana jest metoda notifyObservers która powoduje wywołanie metody update wszystkich obserwatorów. Domyślnie obserwatorzy informowani są o zmianie stanu w kolejności ich zarejestrowania w obiekcie (obserwatorzy zarejestowani jako ostatni są pierwsi informowani o zmianie), natomiast klasa dziedzicząca może zmienić to zachowanie.

Klasa Observable posiada następujące użyteczne metody:
addObserver(Observer o) – dodanie obserwatora. Jak widzimy zgodnie z założeniami wzorca, podmiot nie zwraca uwagi na to jakiego dokładnie typu są obserwatorzy, interesuje go tylko to czy implementują odpowiedni interfejs.
deleteObserver(Observer o) – usuwa obserwatora
boolean hasChanged() - sprawdza czy obiekt się zmienił
notifyObservers() - oraz notifyObservers(Object arg)- jeśli obiekt się zmienił (wywołanie metody hasChanged) wszyscy obserwatorzy informowani są o zmianie stanu. Następnie wywoływana jest metoda clearChanged() (dopóki stan obiektu się nie zmieni, wywołanie metody hasChanged() zwróci false). W przypadku metody pobierającej jako parametr Object arg możemy przesłać obserwatorom zmieniony stan obiektu.
SetChanged() - oznacza stan obiektu jako zmieniony, metoda hasChanged zwróci true.

Czas na prosty przykład użycia opisanych mechanizmów w praktyce:

public class Obserwator implements Observer {
private int numerObserwatora;

public void update(Observable o, Object arg) {
System.out.println(”Poinformowano obserwatora nr ” + numerObserwatora + ” nowy stan podmiotu to:” + (Integer) arg);
}

public Obserwator(int numerObserwatora) {
this.numerObserwatora = numerObserwatora;
}
}

public class Podmiot extends Observable{
private int stan;
public static void main(String []args){
Podmiot podmiot = new Podmiot(0);
Obserwator obserwatorPierwszy = new Obserwator(1);
Obserwator obserwatorDrugi = new Obserwator(2);
Obserwator obserwatorTrzeci = new Obserwator(3);
podmiot.addObserver(obserwatorPierwszy);
podmiot.addObserver(obserwatorDrugi);
podmiot.addObserver(obserwatorTrzeci);
for(int i=0;i<5;i++){
podmiot.setStan(podmiot.getStan()+1);
podmiot.setChanged();
podmiot.notifyObservers(podmiot.getStan());
try {
Thread.sleep(1000);
System.out.println(”Odczekano sekunde”);
} catch (InterruptedException e) {
System.out.println(”Wystapil wyjatek”);
e.printStackTrace();
}
}
}

public void setStan(int stan) {
this.stan = stan;
}

public int getStan() {
return stan;
}

public Podmiot(int stan) {
this.stan = stan;
}
}

Output:

Poinformowano obserwatora nr 3 nowy stan podmiotu to:1
Poinformowano obserwatora nr 2 nowy stan podmiotu to:1
Poinformowano obserwatora nr 1 nowy stan podmiotu to:1
Odczekano sekunde
Poinformowano obserwatora nr 3 nowy stan podmiotu to:2
Poinformowano obserwatora nr 2 nowy stan podmiotu to:2
Poinformowano obserwatora nr 1 nowy stan podmiotu to:2
Odczekano sekunde
Poinformowano obserwatora nr 3 nowy stan podmiotu to:3
Poinformowano obserwatora nr 2 nowy stan podmiotu to:3
Poinformowano obserwatora nr 1 nowy stan podmiotu to:3
Odczekano sekunde
Poinformowano obserwatora nr 3 nowy stan podmiotu to:4
Poinformowano obserwatora nr 2 nowy stan podmiotu to:4
Poinformowano obserwatora nr 1 nowy stan podmiotu to:4
Odczekano sekunde
Poinformowano obserwatora nr 3 nowy stan podmiotu to:5
Poinformowano obserwatora nr 2 nowy stan podmiotu to:5
Poinformowano obserwatora nr 1 nowy stan podmiotu to:5
Odczekano sekunde

Ten prosty program mojego autorstwa zmienia stan obiektu Podmiot, informuje o tym obserwatorów którzy wywołują swoją metodę update, a następnie czeka sekundę przed kolejnym i przeprowadza cały proces po raz kolejny. I tak pięć razy…
Jak widzimy jest to bardzo proste, ale dzięki zastosowaniu gotowych rozwiązań mogliśmy przy minimalnym nakładzie pracy (i czasu! - a to najważniejsze) wykorzystać wzorzec Obserwator.

Wzorce projektowe - Abstract Factory

Posted on May 20, 2008, under wzorce projektowe.

Zaraz po wzorcu Factory Method zabrałem się za analizowanie bardzo powiązanego z nim wzorca Abstract Factory…

Cel wzorca:

Dostarczenie interfejsu do tworzenia grup powiązanych, lub zależnych od siebie obiektów bez określenia ich konkretnego typu.

Odpowiedni gdy:

  • system powinien być niezależny od tego jak jego elementy są tworzone, reprezentowane i z czego się składają
  • system powinien obsługiwać jedną z wielu powiązanych grup elementów
  • grupa elementów została stworzona do działania razem i chcemy to zabezpieczyć (czyli uniemożliwić tworzenie naraz obiektów z kilku grup)
  • chcemy dostarczyć bibliotekę klas i zasłonić przed klientem ich implementację dostarczając mu tylko i wyłącznie odpowiednie interfejsy.

Struktura:

Diagram UML dla wzorca Abstract Factory

AbstractFactory – deklaruje interfejs do operacji tworzących nowe obiekty odpowiedniego typu

KonkretnaFabryka1, KonkretnaFabryka2 – implementuje metody tworzące konkretne produkty odpowiednich typów

Poza interfejsem AbstractFactory i klasami implementującymi ten interfejs w skład wzorca wchodzą interfejsy opisujące poszczególne produkty (w naszym wypadku AbstactProduktA i AbstractProduktB), a także konkretne implementacje tych interfejsów – czyli produkty tworzone przez KonkretnaFabryka1 i KonkretnaFabryka2. Ważne jest to, iż każda KonkretnaFabryka może tworzyć tylko odpowiednie dla niej konkretne typy produktów. (w naszym przypadku KonkretnaFabryka 1 może tworzyć tylko ProduktA1(implementujący AbstractProduktA) oraz ProduktB1(AbstractProduktB))

Klient używa tylko metod interfejsów AbstractFactory, oraz interfejsów implementowanych przez poszczególne produkty – nie ma on świadomości jakiego dokładnie typu jest obiekt którego w danym momencie używa.

Konsekwencje

  • izolowanie klienta od klas implementujących interfejsy
  • wystarczy zmienić rodzaj implementacji AbstractFactory z którego korzystamy, aby dostać zupełnie inną grupę obiektów, ale w korzystaniu z nich nic się nie zmieni.
  • zapewnia iż użytkownik będzie mógł używać tylko jednej rodziny produktów w danej chwili.

Implementacja:

  • w systemie przeważnie potrzebna będzie tylko jedna implementacja AbstractFactory, dlatego przeważnie używa się do tego Singletona.
  • najczęściej jest zdefiniowana FactoryMethod do tworzenia każdego typu obiektu
  • jeśli istnieje dużo rodzin produktów które mogą być tworzone, można skorzystać ze wzorca Prototype.

Użycie:


wzorca tego używa się na przykład gdy obsługa pewnych czynności zależy od stosowanego przez użytkownika systemu operacyjnego. Abstract Factory używa się także, gdy chcemy manipulować wyglądem aplikacji, definiując wiele typów przycisków, suwaków, itd. których wybór zależy od użytkownika – zastosowanie go daje nam pewność, że wszystkie wyświetlane przez nas w danej chwili kontrolki będą tego samego typu (i pozwala nam na łatwą jego zmianę gdy zajdzie taka potrzeba).

Wzorce projektowe - Factory method

Posted on May 19, 2008, under wzorce projektowe.

Po Singletonie, nadszedł czas na kolejny często stosowany wzorzec – Factory Method

Typ wzorca:

Creational

Cel wzorca:

Zdefiniowanie interfejsu do tworzenia obiektów, zezwolenie klasom implementującym dany interfejs na zdecydowanie której podklasy instację chcą stworzyć. Fabryki zajmują się tworzeniem obiektów konkretnego typu, ale ten typ jest podtypem zdefiniowanego interfejsu opisującego ogólny typ tworzonych produktów. (strasznie to zamieszane w teorii ale schemat poniżej jest o wiele czytelniejszy). Polimorfizm powoduje, że nie musimy przejmować się jakiego tworzony obiekt jest podtypu. Tą wiadomość ma tylko metoda która te obiekty tworzy. Dzięki takiemu podejściu tworzeniem obiektów zajmuje się tylko nasz fabryka, w przypadku konieczności wprowadzenia jakichkolwiek zmian, wystarczy że poprawimy kod metody fabryki obiektów, a wszystko inne w programie może pozostać bez zmian. Czasami tworzymy metode fabryki jako statyczną, pozwala nam to na używanie jej bez posiadania obiektu danej klasy, natomiast ogranicza jeśli chcemy rozbudować naszą fabrykę poprzez jej przedefiniowanie w nowej klasie (w javie metody statyczne nie mogą być przeładowywane)

Diagram UML wzorca factory method

Cechy charakterystyczne:

Obiekty nie są tworzone w programie za pomocą operatora new(oczywiście tylko te którymi zajmuje się fabryka) – tym zajmuje się nasza Factory Method.

Stosowany gdy:

  • tworzymy dużo obiektów różnego typu, implementujących dany interfejs
  • klasa która tworzy obiekty nie może przewidzieć jakiego szczegółowego typu obiekt będzie musiała stworzyć
  • klasa chce aby jej podklasy określiły typ konkretny tworzonego obiektu

Uwagi:

Wzorzec ten jest użyteczny także gdy posiadamy tylko jeden typ konkretnego Produktu. Jesteśmy gotowi na dodanie nowych typów produktu, a nawet jeśli nie ma takiej potrzeby korzyścią jest to że cały kod który jest odpowiedzialny za tworzenie obiektów danego typu w programie odpowiada jedna metoda, w przypadku jakichkolwiek zmian wystarczy poprawić jedną metodę. Dzięki zastosowaniu factory method tworzymy kod działający na interfejsach a nie na konkretnych klasach (podejście polecane przy projektowaniu obiektowym)

Metoda fabryki nie koniecznie musi być abstrakcyjna. Możemy stworzyć domyślną metodę, którą tylko niektóre jej podklasy będą przeładowywać. Dozwolone jest także tworzenie sparametryzowanych factory method, która pozwala na tworzenie produktów typu zależnego od parametru (wtedy jedna factory method moze tworzyc obiekty różnych klas (ale wszystkie one implementują ogólny interfejs produktów)).

Użycie metody:

  • Bardzo często używana jest w różnego rodzaju toolkitach i frameworkach.
  • Abstract Factory jest często tworzona z factor method.

Wzorce projektowe - Singleton

Posted on May 18, 2008, under wzorce projektowe.

Na drodze do zielonego pasa na javablackbelt napotkałem nieszczęsny egzamin „OO – Intermed”, który uświadomił mi, że zarówno w czasie studiów, jak i podczas czytania wielu książek dotyczących programowania, nie spotkałem się jeszcze z solidnym omówieniem wzorców projektowych… Słyszałem o wzorcu Singleton czy Factory, natomiast nie do końca wiedziałem na czym wzorce te polegają… Dlatego też, postaram się w tym miejscu omówić wszystkie wzorce których znajomość jest wymagana na javablackbelt.com, może komuś przyda się to krótkie podsumowanie, a ja dzięki temu usystematyzuję swoją wiedzę.

Cały wpis powstał z wykorzystaniem:

  • Design Patterns: Elements of Reusable Object-Oriented Software – prawdziwa legenda jeśli chodzi o pojęcie wzorców projektowych,
  • Head First Design Patterns – nowsze podejście do tematu
  • Angielska Wikipedia – ciekawe przykłady wzorców w javie

A więc zaczynamy… Na pierwszy ogień trafił wzorzec Singleton:

Typ wzorca:

Creational

Cel wzorca:

Zapewnienie tego, że klasa może posiadać tylko jedną instancję, o globalnym zasięgu. Podczas implementacji wzorca, ważne jest także to aby nasza jedyna instancja, była tworzona dopiero gdy będzie potrzebna.

Kiedy używamy:

Wtedy kiedy ważne jest dla nas aby pewna klasa miała tylko jedną instancję – na przykład drukarka w systemie (zakładając że komputer ma dostęp tylko do jednej) – wszystkie dokumenty które chcemy wydrukować powinny mieć dostęp właśnie do tej samej drukarki. Podobnie obiekty obsługujące np. rejestr często definiowane są jako singletony. Alternatywnym wyjściem (chociaż niekoniecznie w javie) byłoby stworzenie jakiegoś globalnego obiektu, natomiast nie zapewniłoby to istnienia nie więcej niż jednej instancji, a w tym wzorcu właśnie na tym nam szczególnie zależy.

Cechy charakterystyczne:

Konstruktor o modyfikatorze z ograniczonym dostępem (private – gdy chcemy zastosować typowy przykład Singletona - natomiast gdy chcemy móc dziedziczyć po signletonie, możemy oznaczyć konstruktor jako protected – co sprawia że nasz singleton nie jest już do końca singletonem) oraz statyczna metoda zwracająca instancję danej klasy.

Przykłady w javie:

najprostsze rozwiązanie:

public class Singleton {
private final static Singleton instance = new Singleton();
//prywatny konstruktor zapobiega tworzeniu przez
//użytkowników dodatkowych instancji
private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

Dzięki temu że obiekt instance jest statyczny, jest on tworzony gdy tylko klasa jest ładowana. Do pobrania stworzonej instancji używamy metody getInstance(). Dzięki istnieniu prywatnego konstruktora, nie jest tworzony automatycznie konstruktor domyślny, a użytkownik sam nie może tworzyć obiektów klasy Singleton za pomoca operatora new. Rozwiązanie powyższe jest bardzo proste, ale mimo to jest bezpieczne w aplikacjach wielowątkowych. Kolejne rozwiązanie powoduje jeszcze późniejsze stworzenie brakującego obiektu, co zgodne jest z naszym założeniem iż instancja singletona powinna być tworzona dopiero gdy będzie potrzebna.

rozwiązanie Billa Pugh’a.

public class Singleton {

private Singleton() {}

private static class SingletonHolder {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

Jest to metoda rekomendowana. Jest to najbardziej uniwersalna wersja implementacji singletona (działa na każdej jvm i w każdym kompilatorze). Dzięki zastosowaniu klasy wewnętrznej obiekt instance wgrywany jest dopiero podczas pierwszego wywołania metody getInstance(). Rozwiązanie jest oczywiście bezpieczne w programach wielowątkowych.

rozwiązanie wykorzystujące możliwości javy 5.0 (lub wyższej)

public class Singleton {
private static volatile Singleton INSTANCE;
privateSingleton() {}
public static Singleton getInstance() {
if (INSTANCE == null)
synchronized(Singleton.class) {
if (INSTANCE == null)
INSTANCE = new Singleton();
}
return INSTANCE;
}
}

Słowo kluczowe volatile zapewnia, że wiele wątków potrafi obsługiwać kod poprawnie. Synchronizowana część metody getInstance zapewnia iż kod ten jest bezpieczny w wielowątkowości – natomiast dzięki warunkowi if(INSTANCE==null) kosztowne czasowo synchronizowanie kodu odbywa się tylko wtedy gdy faktycznie jest potrzebne – czyli gdy nie nie korzystano jeszcze z naszego singletona. Zauważmy iz w synchronizowanym kodzie ponownie sprawdzany jest warunek INSTANCE==null – poprzednio kod nie był synchronizowany i inny wątek mógł już zmienić wartość INSTANCE.

Konsekwencje stosowania wzorca Singleton:

  • Kontrolowany dostęp do jedynej instancji. Dzięki temu że klasa singletona opakowywuje jedyny istniejący obiekt, może ona kontolować i ograniczać klientowi dostęp do tej istancji.
  • Umożliwia łatwe zmienienie ilości dostępnych instancji (dopuszczenie więcej niż jednej). Wymaga to tylko zmiany w metodzie dostępu do tych instancji.
  • Możemy tworzyć także klasy dziedziczące po naszym singletonie i rozszerzające jego funkcjonalność. Klasy takie są bardzo proste w użyciu a dają duże możliwości. (ale teoretycznie, obiekt z konstruktorem innym niż prywanty nie jest już singletonem)

Powiązane wzorce:

  • Wiele wzorców może zostać zaimplementowane z wykorzystaniem Singletona. Na przykład Abstract Factory, Builder albo Prototype.
  • Wzorzec Fasada (Facade) jest przeważnie implementowany jako Singleton, ponieważ tylko jedna fasada jest wymagana.
  • Obiekty stanowe często są singletonami.
  • Singleton często używany jest razem z wzorcem Factory Method – przykładem użycia razem tych wzorców jest AWT w javie, oraz metoda Toolkit.getDefaultToolkit(). Metoda ta zwraca zależny od platformy (Factory Method) singleton.