Observer design pattern jest behawioralnym wzorcem projektowym w którym jeden obiekt nazywany Subject lub Observable informuje wszystkie inne obserwujące go obiekty o zmianach w jego wewnętrznym stanie. Inne obiekty wtedy zareagują na tę zmianę i obsłużą ją zgodnie z wymaganiami. Informowanie najczęściej jest realizowane przez wywołanie jednej z metod obserwatora. Wzorzec jednocześnie bardzo prosty i bardzo przydatny. Zdecydowanie warto wiedzieć jak poprawnie z niego korzystać.
Czym jest Obserwator?
Spróbujmy na początku wyjaśnić sobie zależność między obserwowanym obiektem a obserwatorem. Gdybyśmy chcieli znaleźć analogię dla tej sytuacji w świecie nie-programistycznym moglibyśmy porównać ją do kiosku z gazetami lub strony z ogłoszeniami o pracę. Chcąc kupować najnowsze wydania naszej ulubionej gazety z lokalnego kiosku musielibyśmy co jakiś czas udać się do kiosku i sprawdzić czy nasza gazeta już jest dostępna. Jest to spora strata czasu, gdyż wiele wizyt skończy się powrotem bez gazety. Zamiast tego dużo efektywniej byłoby poprosić Panią z kiosku o poinformowanie nas o tym, iż gazeta jest dostępna. Wtedy będziemy wiedzieli kiedy należy udać się do sklepu. W tym momencie my zostajemy obserwatorem (biernie czekamy na to, aż zostaniemy poinformowani) a Pani z kiosku staje się obiektem typu Subject. Jej zadaniem będzie poinformowanie nas o zmianie stanu jej asortymentu. Gdy taką informację dostaniemy, zareagujemy na nią wycieczką do sklepu i zakupem gazety.

Przykładem już nam nieco bliższym może być również użycie addEventListener
. Nasza funkcja obsługująca dane zdarzenie czeka na to, aż ktoś wykona określoną akcję – dopiero wtedy zostanie uruchomiona. Nie sprawdzamy regularnie czy jakiś przycisk został kliknięty. Czekamy na to.
ReactiveX
Samodzielne zaimplementowanie tego wzorca w przypadku prostych zdarzeń jest dosyć łatwe. Zrobimy to w następnym rozdziale. Jednak w przypadku bardziej złożonych rozwiązań dobrym pomysłem będzie skorzystanie z biblioteki RxJS. Jest to JavaScript-owa implementacja popularnej biblioteki ReactiveX. RxJS (a tym samym wzorzec obserwatora) jest też często wykorzystywany do obsługi asynchroniczności w Reduxie. Początkowo RxJS może wydawać się dość skomplikowany, jednak po opanowaniu podstaw od razu dostrzeżemy ogromny potencjał jaki daje nam korzystanie z obserwatorów.
Przykład
Jak widać sama idea Observer design pattern nie jest skomplikowana. Jeden z obiektów w naszej aplikacji (znany jako Subject) posiada listę obiektów, które obserwują zmiany w jego stanie. Gdy takowa zmiana nastąpi, Subject wysyła informację o zmianie do wszystkich zainteresowanych. W jednym z poprzednich rozdziałów wspomnieliśmy o portalu z ogłoszeniami o pracę. Zaimplementujmy więc takie rozwiązanie. Portal z ogłoszeniami będzie Subjectem (Observable), natomiast osoby szukające pracy będą obserwatorami. Obserwatorzy będą czekać na informację o nowych ofertach pracy. Poniżej znajduje się bardzo prosta implementacja wraz z komentarzami.
// "Subject" - obiekt który "powiadamia" obiekty o zmianie swojego stanu. // Robi to wywołując metody należące do "obserwatorów" class JobsPortal { constructor() { // o zmianie tego stanu będziemy informować słuchaczy this.newJobOffer = {}; // Subject wie o wszystkich obserwujących go obiektach this.observers = []; } addCandidate(observer) { // metoda służąca dodaniu obserwatora console.log(`${observer.name}, welcome to our job portal!`); this.observers.push(observer); } removeCandidate(observer) { // metoda służąca usunięciu obserwatora let index = this.observers.findIndex(o => o === observer); if (index !== -1) { console.log( `${this.observers[index].name}, good bye and good luck in new job!` ); this.observers.splice(index, 1); } } notify() { // powiadomienie o zmianie stanu... console.log(`We have new job offer!!!`); this.observers.forEach(observer => { // ... jest wysyłane wszystkim obserwatorom observer.sendJobOffer(this.newJobOffer); }); } addNewOffer(offer) { // dodanie nowego ogłoszenia = zmiana stanu this.newJobOffer = offer; // po dodaniu ogłoszenia wyślijmy wszystkim powiadomienia this.notify(); } } // Obserwator - kandydat szukający pracy // to on będzie nasłuchiwał na wiadomości płynące z obiektu "Subject" class Candidate { constructor(name) { this.name = name; // imię kandydata } // metoda którą będzie wywoływal "Subject" // w celu poinformowania o zmianie jego stanu sendJobOffer(offer) { console.log( `${this.name}, new job offer for you: ${offer.title} with ${ offer.sallary } PLN sallary` ); } } // tworzymy nową instację naszego portalu z ogłoszeniami let portal = new JobsPortal(); // w naszym portalu rejestrują się nowi użytkownicy let adrian = new Candidate("Adrian"); let gracja = new Candidate("Gracja"); let piotr = new Candidate("Piotr"); // dodajemy nowych użytkowników do listy obserwatorów portal.addCandidate(adrian); portal.addCandidate(gracja); portal.addCandidate(piotr); // dodajemy nowe ogłoszenie o pracę // nasz portal zajmie się już wysłaniem tego ogłoszenia // do wszystkich zainteresowanych portal.addNewOffer({ title: "Frontend developer", sallary: "15k" }); // jeden kandydat znalazł pracę // więc usuwamy go z listy obserwatorów portal.removeCandidate(piotr); // dodajemy kolejne ogłoszenie // i zostaje ono automatycznie wysłane do pozostałych kandydatów portal.addNewOffer({ title: "Backend developer", sallary: "10k" });

Podsumowanie
Observer design pattern jest dosyć często używanym wzorcem projektowym JavaScript. Najpopularniejsze obecnie frameworki front end-owe, czyli React oraz Vue również bazują właśnie na tym wzorcu. Większość zdarzeń odbywa się w odpowiedzi na inne zdarzenia. Wzorzec obserwatora warto znać, zrozumieć i przede wszystkim – poprawnie stosować.
Świetnie wyjaśnione, dzięki 😉
Dzięki 🙂 Cały czas próbuję znaleźć czas na dokończenie tej serii, ale nie jest z tym najłatwiej ostatnio