Główne logo strony
📅

Wzorzec: Observer design pattern

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 frontend-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ć.

Masz uwagi lub sugestie do tego wpisu?

discord iconPrzejdź na Discord