Wzorzec projektowy Obserwator

Stosowalność

Wzorzec obserwator jest stosowany przy projektowaniu środowisk prezentacji danych.

Intencja:

Definiuje "jeden-do-wielu" zależności między obiektami. Zmiana stanu jednego obiektu automatycznie propaguje na związane z nim obiekty.

Problem:

Należy powiadomić zmieniającą się listę obiektów o pewnym zdarzeniu.

Rozwiązanie:

Obiekty klasy obserwator przekazują odpowiedzialność za monitorowane zdarzenia centralnemu obiektowi dana.

Struktura

Uczestnicy i współpracownicy:

Obiekt klasy dana wie, które obiekty klasy obserwator należy zawiadomić, ponieważ rejestrują się u niego. Dana zawiadamia obiekty klasy obserwator o wystąpieniu zdarzenia. Obiekty klasy obserwator są odpowiedzialne za zarejestrowanie się u obiektu dana i uzyskanie od niego potrzebnej informacji, gdy zostaną powiadomione o zdarzeniu.

Konsekwencje:

Obiekt klasy dana może niepotrzebnie powiadamiać o zdarzeniu różne rodzaje obserwatorów nawet wtedy, gdy są zainteresowane jego wystąpieniem tylko w pewnych przypadkach.

Implementacja:

Tworzy obiekty klasy obserwator, które mają być informowane o wystąpieniu pewnego zdarzenia i w tym celu rejestrują się u obiektu klasy dana. Kiedy zachodzi zdarzenie, dana powiadamia zarejestrowane obiekty klasy obserwator.

  • Klasa dana - zna swoich obserwatorów, dowolna liczba obserwatorów może obserwować dane. Obserwatorzy są rejestrowani - przyłącz, rozłącz (wskazanie na klasę). Metoda powiadom informuje wszystkie zarejestrowane obiekty o zmianach wewnątrz niej.
  • Klasa obserwator - definiuje interfejs dla obiektów, które mają być powiadamiane o zmianach w danych. Obserwator po otrzymaniu informacji o konieczności aktualizacji wysyła żądanie do klasy dana z prośbą o podanie aktualnego stanu. Powiązanie na poziomie klas konkretnych daje możliwość komunikacji od klas obserwujących do danych.
  • Klasa Konk_obserwator - zawiera wskazanie na konkretne dane oraz stan, który powinien być spójny z danymi.
  • Klasa Konkretna_dana - powiadamia obserwatorów o zmianie stanu, przechowuje stan

Zalety

Osłabienie zależności: skutecznie izoluje podsystemy, ponieważ klasy tworzące jeden podsystem nie muszą mieć szczegółowej wiedzy na temat innych.

Łatwość protokołu: obserwator tworzy regułę że jest on wyłącznie jedynym scentralizowanym punktem dostępu. Zależność jeden do wielu jest bardziej dogodna niż wiele do wielu między kolegami.

Wady

Trudność rezygnacji: nie ma pewności że abonent nie zostanie powiadomiony o zdarzeniu już po swojej rezygnacji z abonamentu.

Wycieki pamięci: W języku Java, w którym nie ma metod destruktorów, zagwarantowanie, że wydawcy będą na czas powiadamiani o rezygnacji poszczególnych abonentów jest stosunkowo trudne (jednocześnie łatwo zapomnieć o anulowaniu abonamentu za pomocą bezpośredniego wywołania).

Przykłady

Konto bankowe - Rachunek klienta może być powiązany z innymi rachunkami pobocznymi. Na przykład, z głównym rachunkiem jest związany rachunek kredytu odnawialnego. Wysokość otwartej linii kredytowej zależy od środków na rachunku głównym. Zmiana wysokości salda na tym rachunku skutkuje podwyższeniem lub obniżeniem dostępnej kwoty kredytu.

Wykresy w Excelu - zmiana wartości komórki w tabeli powoduje natychmiastową zmianę wykresu.

Podsumowanie

Wzorzec obserwator wprowadza abstrakcyjne powiązania z podmiotem. Podmiot nie zna szczegółów działania żadnego z obserwatorów. Może więc się okazać, że wobec wystąpienia szeregu przyrostowych zmian danych podmiotu zostanie wysłana do obserwatora seria powtarzających się komunikatów, których obsługa wiązać się będzie ze zbyt dużym kosztem. Rozwiązaniem problemu będzie oczywiście wprowadzenie pewnej dodatkowej logiki, tak by informacje o zmianach nie były wysyłane zbyt wcześnie lub zbyt często. Inny problem występuje w przypadku, gdy zmiana danych podmiotu dokonywana jest przez pewne części kodu lub systemu zwane dalej klientami. Pojawia się wtedy pytanie, kto powinien inicjować wysłanie komunikatu o zmianach. Jeśli odpowiedzialny będzie za to, jak dotychczas, sam podmiot, to w przypadku wykonywania zmian przez kilku klientów znowu mogą pojawić się serie komunikatów o nieznacznych w istocie zmianach. Można ich uniknąć, jeśli to klient będzie informował podmiot, ze należy wysłać komunikat. Jeśli jednak któryś z klientów "zapomni" o poinformowaniu podmiotu, to program nie będzie już działał zgodnie z oczekiwaniami. Stosując wzorzec obserwator mona także zdefiniować kilka rodzajów komunikatów. W tym celu interfejs obserwatora może definiować kilka różnych metod powiadomienia. Dzięki temu w pewnych sytuacjach obserwator będzie mógł ignorować niektóre z nich. Przykładem zastosowania mogą być tu dane statystyczne oraz ich reprezentacje w postaci np. arkusza danych, wykresu słupkowego, wykresu kołowego, itp. .Przy zastosowaniu wzorca Obserwator oddzielamy logiczną strukturę danych od jej reprezentacji, unikając trwałych połączeń między klasami i umożliwiając tym samym ponowne wykorzystanie poszczególnych klas. Jednocześnie unikamy nieczytelnego i zbyt mocno rozbudowanego kodu w pojedynczej klasie.

Przykład

class Obserwator {
public:
  void event(Event &e)
  { e.procsess(); }
};

class Temat {
  Lista _list;
public:
  void dodaj(Obserwator *o) Lista.dodaj(o);
  void usun(Obserwator *o) Lista.usun(o);

  void powiadom_obserwatorow(Event &e)
  {
    for(int i=0; i < _list.count(); i++)
      _list[i].notify(event);
  }
};

Sposób użycia:

Temat* temat = new Temat();
Obserwator *o1 = new Obserwator();
Obserwator *o2 = new Obserwator();
temat->dodaj(o1);
temat->dodaj(o2);

Przykład zastosowania wzorca Obserwator dla systemu aukcji internetowej



Diagram 1

Diagram 2