Wzorzec projektowy Observer
Definicja wzorca Observer
Wzorzec Observer (Obserwator) – definiuje pomiędzy obiektami relację jeden-do-wielu w taki sposób, że kiedy wybrany obiekt zmienia swój stan, to wszystkie jego obiekty zależne zostają o tym powiadomione i automatycznie zaktualizowane.
Przed zastosowaniem wzorca
Jako przykład stwórzmy klasę Pizza reprezentującą restaurację, której zadaniem jest informowanie kierowców o tym, że zamówiona pizza jest już gotowa aby dostarczyć ją klientowi. Oto przykładowa implementacja…
public class Pizza {
Driver driver1 = new Driver("Pieter");
Driver driver2 = new Driver("Andrzej");
public void update() {
driver1.update();
driver2.update();
}
}
Klasa posiada zmienne obiektowe typu Driver reprezentujące kierowców oraz jedną metodę update(), która wywoływana jest w momencie gdy pizza jest gotowa do dostarczenia.
Spójrzmy jeszcze na przykładową implementację klasy Driver.
public class Driver {
private String name;
public Driver(String name) {
this.name = name;
}
public void update() {
System.out.println(name + ": otrzymałem informacje!");
}
}
Klasa Driver posiada pole name opisujące imię kierowcy oraz metodę update(), która wyświetla przykładową informację.
Stwórzmy obiekt klasy Pizza i poinformujmy kierowców o tym, że pizza jest gotowa…
public class MainObserver {
public static void main(String[] args) {
boolean isPizzaReady = true;
Pizza pizza = new Pizza();
if (isPizzaReady) {
System.out.println("Pizza gotowa!");
pizza.update();
}
}
}
Jak widzimy wszystko działa jak należy i każdy kierowca jest poinformowany o gotowej pizzy. Problem jednak pojawia się gdy chcemy dodać jeszcze jednego kierowcę, który również byłby informowany o gotowej pizzy. Musielibyśmy wtedy wprowadzać poprawki w implementacji klasy Pizza, które dodadzą kolejnego kierowcę…
public class Pizza {
Driver driver1 = new Driver("Pieter");
Driver driver2 = new Driver("Andrzej");
Driver driver3 = new Driver("Janek");
public void update() {
driver1.update();
driver2.update();
driver3.update();
}
}
A co jeśli za chwilę okazałoby się, że należy dodać kolejnego kierowcę lub usunąć jednego z nich. Znowu zmiana.
Jak widać takie rozwiązanie jest nie efektywne i każde nowe zachowanie wymusza wprowadzenie zmian w kodzie. Rozwiązaniem jest zastosowanie wzorca Observer.
Po zastosowaniu wzorca
Aby zastosować wzorzec Observe na początku stwórzmy interfejs Observer, który będzie zawierał jedną metodę abstrakcyjną update().
public interface Observer {
void update();
}
Następnie stwórzmy klasę DriverObserver, która implementuję ten interfejs. Będzie to klasa reprezentująca kierowcę naszej restauracji. Zawiera ona pole name, które przechowuje imię kierowcy oraz zaimplementowaną metodę update(), która wyświetla informację.
public class DriverObserver implements Observer {
private String name;
public DriverObserver(String name) {
this.name = name;
}
@Override
public void update() {
System.out.println(name + ": otrzymałem informacje!");
}
}
Stwórzmy jeszcze jeden interfejs Observable, który będzie zawierał trzy metody abstrakcyjne: addObserver(), removeObserver() oraz notifyObserver().
public interface Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
Oraz klasę PizzaObservable, która implementuje interfejs Observable i nadpisuje metody abstrakcyjne. Ta klasa reprezentować będzie naszą restaurację (Pizzerię).
public class PizzaObservable implements Observable {
private ArrayList observers;
public PizzaObservable() {
observers = new ArrayList();
}
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
if (observers.contains(observer)) {
observers.remove(observer);
}
}
@Override
public void notifyObservers() {
for (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer) observers.get(i);
observer.update();
}
}
}
Pole observers zawiera listę obserwatorów (w naszym przypadku kierowców) przypisanych do restauracji. Metoda addObserver() dodaje nowego obserwatora do listy, metoda removeObserver() usuwa obserwatora z listy, natomiast metoda notifyObservers() informuję wszystkich przypisanych obserwatorów o gotowej pizzy.
Zobaczmy teraz jak działa powiadamianie kierowców o gotowej pizzy w metodzie main().
public class MainObserver {
public static void main(String[] args) {
boolean isPizzaReady = true;
PizzaObservable pizzaObservable = new PizzaObservable();
DriverObserver driver1 = new DriverObserver("Pieter");
DriverObserver driver2 = new DriverObserver("Andrzej");
pizzaObservable.addObserver(driver1);
pizzaObservable.addObserver(driver2);
if (isPizzaReady) {
System.out.println("Pizza gotowa!");
pizzaObservable.notifyObservers();
}
}
}
Na początku tworzymy obiekt pizzaObservable, który jest naszą restauracją. Następnie tworzymy dwa obiekty driver1 i driver2, które przedstawiają kierowców. Kolejnym krokiem jest dodanie tych kierowców do listy naszej restauracji poprzez wywołanie metody addObserver() na obiekcie pizzaObservable. Teraz, gdy pizza jest gotowa (co symuluję zmienna isPizzaReady) zostaję wywołana metoda notifyObservers(), która automatycznie informuję wszystkich przypisanych do restauracji kierowców o gotowym produkcie, wywołując na każdym z tych obiektów jego własną metodę update().
Dzięki takiemu rozwiązaniu dodanie nowego kierowcy lub usunięcie jednego z obecnych sprowadza się tylko do wywołania odpowiedniej metody bez żadnej przeróbki w kodzie.
Jak widać zastosowanie wzorca Observer daje możliwość dynamicznego przypisywania nowych obiektów i sprawia, że kod staję się łatwiejszy w utrzymaniu i daję się łatwo rozbudowywać.
Zalety wzorca Observer
- Luźne powiązanie obiektu obserwowanego i obiektów obserwujących,
- Relacja między obiektem obserwowanym a obserwatorem tworzona jest podczas wykonywania programu i może być dynamicznie zmieniana,
- Obiekt obserwowany jest zwolniony z zarządzania subskrypcją – o tym czy obsłużyć powiadomienie, decyduje sam obserwator.
Wady wzorca Observer
- Obserwatorzy nie znają innych obserwatorów, co może prowadzić do powstania trudnych do znalezienia nieprawidłowości.
Kiedy stosować wzorzec
Wzorzec Observer należy stosować w sytuacji kiedy zmiana w jednym obiekcie wymaga zmodyfikowania drugiego, przy czym nie wiadomo ile obiektów trzeba przekształcić. A także, jeżeli obiekt powinien móc powiadamiać inne obiekty bez określania ich rodzaju.