Czytasz jeden z artykułów opisujących wzorce projektowe. jeżeli interesuje Cię ten temat zapraszam Cię do lektury pozostałych artykułów, które powstały w ramach tej serii – wzorce projektowe. W zrozumieniu artykułu przyda Ci się wiedza dotycząca podstaw UML’a.
Problem do rozwiązania
Czytasz artykuły na różnych stronach internetowych. Jedną z tych stron jest Samouczek Programisty ;). Są strony na które zaglądasz regularnie. Raz na jakiś czas sprawdzasz czy na stronach, które Cię interesują nie pojawiły się nowe artykuły. Po lekturze nowych artykułów spisujesz swoje notatki. jeżeli stron do śledzenie masz sporo pojawia się problem. Regularne sprawdzanie czy pojawiły się nowe treści jest mało efektywne. Możesz rozwiązać ten problem na kilka sposobów, jednym z nich może być zapisanie się do newslettera. Można powiedzieć, iż zapisanie się na newsletter czyni z Ciebie obserwatora strony.
Ten sam problem występuje w projektach informatycznych. Istnieją zdarzenia, które powinny wyzwalać pewne zachowanie. Wystąpienie zdarzenia powoduje to, iż obserwator aktualizuje swój stan na podstawie zmiany obserwowanego elementu. Aktywne sprawdzanie czy zdarzenie wystąpiło w większości przypadków nie jest dobrym rozwiązaniem. W projektach informatycznych problem tego typu rozwiązany jest przez wzorzec projektowy obserwator (ang. observer).
Wzorzec obserwator
Diagramy klas
Ten wzorzec projektowy opiera się o dwa interfejsy. Jeden z nich reprezentuje obserwatora. Drugi element, który jest obserwowany:
Interfejs Observable zawiera trzy metody:
- attach(Observer) – powoduje dodanie nowego obserwatora (obserwator jest zainteresowany zmianami),
- detach(Observer) – powoduje usunięcie istniejącego obserwatora (obserwator nie jest już zainteresowany zmianami),
- notify() – powoduje powiadomienie wszystkich obserwatorów o wystąpieniu zmiany.
Interfejs Observer zawiera wyłącznie jedną metodę:
- update() – metoda jest wywołana przez Observable w momencie wystąpienia zmiany.
Interfejsy nie przechowują żadnego stanu, który może się zmienić. adekwatne obiekty implementują te interfejsy i to one przechowują stan.
Pobierz opracowania zadań z rozmów kwalifikacyjnych
Przygotowałem rozwiązania kilku zadań algorytmicznych z rozmów kwalifikacyjnych. Rozkładam je na czynniki pierwsze i pokazuję różne sposoby ich rozwiązania. Dołącz do grupy ponad 6147 Samouków, którzy jako pierwsi dowiadują się o nowych treściach na blogu, a prześlę je na Twój e-mail.
Przykładowa implementacja obserwatora
Interfejsy przedstawione na diagramie UML mogą wyglądać następująco:
Posłużę się przykładem, który przytoczyłem na początku artykułu. Wyobraź sobie blog, na którym publikowane są artykuły. Blog pozwala się obserwować – implementuje interfejs Observable. W momencie dodania nowego czytelnika zostaje on dodany do zbioru obserwatorów.
Następnie w momencie publikacji nowego artykułu (metoda publishArticle) zmieniany jest wewnętrzny stan instancji klasy Blog. Po tej zmianie wywołana jest metoda notifyObservers. Wewnątrz tej metody na każdej z instancji implementującej Observer wywołana jest metoda update:
Obserwatorem jest czytelnik reprezentowany przez klasę Reader. Czytelnik wie jaki zasób obserwuje, przechowuje go w atrybucie blog. W momencie powiadomienia, czyli w trakcie wywołania metody update, sprawdzany jest stan atrybutu blog i Reader może odpowiednio na tę zmianę zareagować. W tym przypadku informuje o najnowszym artykule:
Przekładając klasy z tego przykładu na te użyte w diagramie UML:
- SomeObservable – Blog,
- SomeObserver – Reader.
Dodatkowe rozważania
Obserwator to wzorzec, który jest bardzo generyczny. W swojej podstawowej wersji nie posiada mechanizmu na informowanie o tym co dokładnie zmieniło się w obserwowanym obiekcie. Takie podejście ma swoje wady i zalety.
Zalety
Jedną z zalet stosowania tego wzorca projektowego jest to, iż klasa implementująca interfejs Observer nie musi aktywnie sprawdzać czy interesujący ją obiekt się zmienił.
Dzięki zastosowaniu tego wzorca projektowego można w czysty sposób odizolować od siebie obiekty. Nie są one ze sobą sztywno powiązane. Dodatkowo szeroka definicja metody update pozwala na informowanie o zdarzeniach różnego rodzaju.
Niewątpliwą zaletą także jest to, iż obiekt obserwowany może poinformować wielu obserwatorów używając tego samego protokołu.
Wady
Obserwator powiadomiony o zmianie sam musi dojść do tego co się zmieniło w obiekcie obserwowanym. Czasami takie sprawdzenie może nie być trywialne. Co więcej nie jest to potrzebne, bo obserwowany obiekt doskonale wie co się zmieniło – sam przecież o tej zmianie informuje swoich obserwatorów.
Można to obejść poprzez rozszerzenie metody attach lub update. Na przykład zmiana deklaracji z attach(Observer observer) na attach(Observer observer, EnumType event) może informować obiekt informowany o tym, iż dany obserwator zainteresowany jest jedynie podzbiorem zdarzeń.
Podobną zmianę można wprowadzić w metodzie update zmieniając ją z update() na update(EventDetails eventDetails). Zmiany tego typu sprawiają, iż interfejsy Observable czy Observer nie są już tak generyczne.
Przy synchronicznym powiadamianiu obserwatorów może wystąpić sytuacja, w której wywołania metody update zajmują lwią część czasu zmiany stanu obiektu obserwowanego.
Przykłady użycia wzorca obserwator
W standardowej bibliotece języka Java możesz spotkać całą masę różnych implementacji interfejsu EventListener. Jest to interfejs bazowy dla pozostałych interfejsów, które służą do informowania o wystąpieniu pewnego zdarzenia. To nic innego jak Observer, z rozszerzoną metodą update.
Jeśli udało Ci się już przeczytać artykuł o wątkach to wiesz o mechanizmie powiadamiania. Także tam można dopatrzeć się analogii do wzorca projektowego obserwator. Wątek, oczekujący na pewien zasób jest powiadamiany kiedy zasób staje się dostępny.
Można powiedzieć, iż MVC (ang. Model View Controller) jest wzorcem architektonicznym. Połączenia pomiędzy poszczególnymi komponentami można uzyskać stosując wzorzec obserwatora. Na przykład widok obserwuje zmiany w modelu, model informuje widok o zmianach, które powinny zostać pokazane użytkownikowi.
Ćwiczenie do wykonania
Ćwiczenie polega na zaimplementowaniu klasy zdarzenia ArticleEvent, która będzie zawierała informacje o nowym artykule opublikowanym na blogu. Wymaga to także zmiany metody update. Niech obserwator użyje informacji przekazywanej w tym zdarzeniu do pokazania najnowszego artykułu. Czy w takim przypadku Reader potrzebuje instancji klasy Blog?
Dodatkowe materiały do nauki
Niezmiennie, we wszystkich artykułach z serii poświęconej wzorcom projektowym polecam książkę Design Patterns – Gamma, Helm, Johnson, Vlissides. jeżeli miałbym polecić wyłącznie jedno źródło to poprzestałbym na tej książce.
Możesz też przeczytać więcej o obserwatorze z innego punktu widzenia. Wartościowym źródłem są także artykuły na polskiej i angielskiej Wikipedii.
Zachęcam Cię też do zajrzenia do kodu źródłowego, który użyłem w tym artykule.
Podsumowanie
Po lekturze tego artykułu wiesz czym jest obserwator. Artykuł pokazał Ci też pewne wariacje tego wzorca projektowego. Po wykonaniu ćwiczenia potrafisz zaimplementować swój własny obserwator. Można powiedzieć, iż udało Ci się poznać kolejny wzorzec projektowy. Gratulacje!
Czy udało Ci się użyć tego wzorca w praktyce? W czym pomógł w Twoim projekcie? Podziel się Twoją opinią z innymi Czytelnikami :).
Jeśli znasz kogoś komu obserwator może się przydać proszę podziel się odnośnikiem do tego artykułu. Kto wie, może dzięki Tobie Samouczek zyska kolejnego Czytelnika? Z góry dziękuję!
Jeśli nie chcesz pominąć kolejnych artykułów proszę dopisz się do samouczkowego newslettera i polub profil Samouczka na Facebook’u. To tyle na dzisiaj, trzymaj się i do następnego razu!