Wzorzec projektowy Obserwator


Wprowadzenie

Obserwator (Observer) jest behawioralnym wzorcem projektowym. Jego głównym zadaniem jest przekazywanie informacji o zmianie stanu obiektu innym zainteresowanym obiektom. W praktyce stosuje się go głównie w sytuacjach, w których mamy do czynienia z relacją jeden do wielu, gdzie wiele obiektów musi być informowanych o zmianie stanu obiektu głównego.

Problem

Budujemy aplikację prognozy pogody, która dostarcza aktualne dane do różnych mediów: serwisów internetowych, stacji radiowych oraz telewizji. Każda z tych platform chce być automatycznie informowana o zmianie prognozy.

Naturalnym, ale problematycznym rozwiązaniem byłoby bezpośrednie powiązanie klasy WeatherForecast z każdą klasą reprezentującą konkretne medium. Oznaczałoby to, że klasa prognozy pogody musiałaby znać wszystkie typy odbiorców i wywoływać ich metody ręcznie.

W takim podejściu każda nowa platforma wymagałaby modyfikacji istniejącego kodu. System staje się silnie powiązany, a jego rozwój coraz trudniejszy.

Rozwiązanie

Wzorzec projektowy Obserwator rozwiązuje ten problem poprzez wprowadzenie relacji jeden-do-wielu między obiektem obserwowanym, a jego obserwatorami.

Obiekt WeatherForecast nie zna konkretnych klas mediów. Współpracuje jedynie z obiektami spełniającymi interfejs obserwatora. W Pythonie interfejs ten definiujemy jako klasę abstrakcyjną (ABC), która określa wymagane metody.

Gdy stan obiektu ulega zmianie, WeatherForecast powiadamia wszystkich zarejestrowanych obserwatorów, wywołując ich metodę aktualizacji.

Dzięki temu:

  • można dynamicznie dodawać i usuwać obserwatorów,
  • nie ma potrzeby modyfikowania klasy prognozy pogody przy dodawaniu nowych mediów,
  • zmniejszamy sprzężenie między klasami,
  • spełniamy zasadę Open/Closed z SOLID.

Implementacja

Struktura wzorca

Poniższy diagram UML dzieli się na dwie zasadnicze części:

  • lewa część odpowiedzialna za obiekt obserwowany (podmiot) – obiekt klasy WeatherForecast implementujący interfejs Observable,
  • prawa część odpowiedzialna za obserwatorów obiektu – obiekty klas InternetNews, TvNews i RadioNews, implementujące interfejs Observer.
wzorzec projektowy obserwator

Struktura plików

  • observer
    • observer.py
    • observable.py
    • weather_forecast.py
    • internet_news.py
    • radio_news.py
    • tv_news.py
    • main.py

Kod źródłowy

observer.py

observable.py

weather_forecast.py

internet_news.py

radio_news.py

tv_news.py

main.py

Wynik działania:

Podsumowanie działania

Na powyższym przykładzie najpierw dodano obserwatorów do podmiotu. Następnie wszyscy obserwatorzy zostali powiadomieni o prognozie pogody. Później jednak pozostawiono tylko jednego obserwatora, który otrzymał prognozę po raz drugi.

Klient ma możliwość dodawania i usuwania obiektów obserwujących podmiot, dlatego liczba obserwatorów nie musi być znana na samym początku. Dodatkowo można w dowolnym momencie dodawać do aplikacji nowe klasy implementujące interfejs Observer bez konieczności modyfikacji istniejącego kodu – spełniamy wtedy zasadę Open/Closed (drugą zasadę SOLID).

Kiedy nie używać wzorca

Choć wzorzec Obserwator jest bardzo użyteczny, nie zawsze jest najlepszym rozwiązaniem.

Przede wszystkim nie sprawdzi się w sytuacjach, gdy liczba powiadomień jest bardzo duża i może powodować problemy wydajnościowe. Każda zmiana stanu obiektu powoduje bowiem wywołanie metody update() u wszystkich obserwatorów. W związku z tym w rozbudowanym systemie może to prowadzić do trudnych do kontrolowania zależności.

Zaletą wzorca jest niskie sprzężenie między komponentami. Jednakże ceną za tę elastyczność jest trudniejszy do prześledzenia przepływ sterowania – skutki zmiany stanu są rozproszone po wielu klasach, a więc nie zawsze łatwo przewidzieć pełne konsekwencje jednej operacji.