Press "Enter" to skip to content

Wzorce projektowe – Obserwator

Wstęp i definicja

Wzorzec obserwator jest kolejnym wzorcem należącym do grupy wzorców czynnościowych. Stosujemy go wtedy, kiedy chcemy powiadomić inne moduły o zmianach, które nastąpiły w naszym systemie.

„Wzorzec Obserwator definiuje pomiędzy obiektami relację jeden-do-wielu w taki sposób, że kiedy wybrany obiekt zmienia swój stan, to wszystkie jego obiekty zależne zostają o tym powiadomione i automatycznie zaktualizowane.”

Definicja zaczerpnięta z książki Rusz głową! Wzorce projektowe

Schemat wzorca obserwator

System zarządzania karetkami

Wyobraźmy sobie system, w którym kierowcy karetek są automatycznie informowani o zdarzeniach podczas obierania zgłoszenia przez dyżurnego. W sytuacji takiej naszym Podmiotem będzie Dyżurny a Obserwatorami będą Karetki. Karetki czekają w swoich wyznaczonych miejscach na zmianę stanu w dyspozytorni. Żadnemu pracownikowi nie chce się jednak co chwilę sprawdzać, czy dyżurny wystawił jakieś zgłoszenie. Dodatkowo, jeśli wszystkie karetki zaczęłyby sprawdzać takie informacje w tym samym momencie, mogłoby to spowolnić działanie systemu. Znacznie lepszym rozwiązaniem jest tutaj zaimplementowanie sytuacji, w której to dyżurny będzie informował karetki o zaistniałych zgłoszeniach.
Na takiej zasadzie działa wzorzec Obserwator. Kiedy ktoś zadzwoni na numer alarmowy, dyżurny odbiera połączenie i po ustaleniu zdarzenia, zmienia swój stan oraz informuje o tym swoje karetki za pomocą na przykład CBRadia. Karetki mogą się dopisywać do listy oraz mogą z niej odchodzić (na przykład podczas zmiany dyżurów pracowników karetek, lub zajętości podczas wykonywania zadania).

Schemat systemu zarządzania karetkami

W ten sposób mamy prosty schemat obsługujący nasz system do zarządzania karetek. Nic nie stoi na przeszkodzie, aby dodać kolejne karetki lub inne służby ratownicze, jak chociażby policję lub straż pożarną. Oczywiście wszystkie karetki posiadają powiązanie z obiektem operatora, którym w tym przypadku jest Adam, strzałki nie zostały uwzględnione, aby nie zanieczyścić czytelności diagramu. W sytuacji, w której zmieniają się pracownicy, operator również może się zmienić i nie będzie to wpływało na działanie całego systemu.

Wzorzec Obserwator w języku JAVA

Język JAVA posiada w sobie implementację omawianego dzisiaj wzorca projektowego. Oczywiście w JAVIE zaimplementowanych jest kilka rodzajów takich Obserwatorów, ponieważ różne moduły języka potrzebowały różnych implementacji tego wzorca. Za pomocą najbardziej powszechnej wersji tego języka można rozwiązać niemal każdy problem, z którym się spotkamy. Oto schemat zaimplementowanego wzorca Obserwator w języku JAVA z pakietu java.util w połączeniu z naszą aplikacją.

Schemat zaimplementowanego wzorca obserwator w języku JAVA

Jak widać na załączonym wyżej schemacie, Operator nie implementuje w tym przypadku interfejsu, lecz dziedziczy po klasie abstrakcyjnej Observable. Rozwiązanie takie ma swoje wady oraz zalety. Jako zaletę można uznać fakt, iż w implementacjach naszych podmiotów, nie musimy implementować własnej logiki, która będzie odpowiedzialna za rejestrowanie, wyrejestrowywanie, powiadamianie obserwatorów, etc. Do wad należy dodać, że niestety korzystamy tutaj z dziedziczenia. Modelu tego nie będziemy mogli wykorzystać w sytuacji, w której nasza klasa dziedziczy już po jakiejś klasie. W takiej sytuacji będziemy zmuszeni do napisania własnej implementacji opisanego wzorca projektowego.

Metody zaimplementowanego wzorca

W zaimplementowanym w języku JAVA wzorcu obserwatora, podmiot posiada dwie metody powiadamiające obserwatorów o zmianie stanu:

  • notifyObservers()
  • notifyObservers(Object arg)

Obie metody służą do powiadamiania obserwatorów, jednak druga wersja przekazuje dodatkowo tak zwany Obiekt danych, czyli obiekt, który przechowuje wszystkie parametry zmienionego obiektu.
Dodatkową metodą, którą posiada podmiot wzorca, jest metoda setChanged(). Metoda ta ustawia zmienną typu boolean na wartość true, jeśli dane w obiekcie uległy zmianie. Zastosowanie takie sprawdza się w sytuacjach, kiedy obiekt zbiera dane z bardzo czułych sensorów (na przykład stan paliwa w samochodzie). Zmiana stanu obiektu byłaby w tym momencie bardzo uciążliwa, ponieważ każdy mililitr zmiany sprawiałby powiadamianie wskaźnika na kokpicie samochodu. W takiej sytuacji wystarczy zaimplementować logikę, która będzie aktualizowała wskaźnik, jeśli w zbiorniku zmieni się poziom paliwa na przykład o litr.
Metoda update() interfejsu Observer również wygląda nieco inaczej. Pobiera ona dwa parametry typów:

  • Observable
  • Object

Pierwszy parametr jest obiektem podmiotu, którego obserwują obserwatorzy, drugi zaś jest wspomnianym wcześniej obiektem danych. Jeśli obiekt danych nie istnieje, należy przesłać wartość null. Ważne jest aby w metodzie programu zabezpieczyć się przed niechcianymi wyjątkami wyrzuconymi podczas próby dostępu do nieistniejącego obiektu.

Podsumowanie

Wzorzec obserwator jest wykorzystywany wszędzie tam, gdzie istnieje potrzeba aktualizacji pewnych danych. W języku JAVA powszechnie stosowany jest w bibliotece Swing. Każdy listener, który nasłuchuje na zdarzenie użytkownika jest swojego rodzaju obserwatorem, natomiast obiekt, które wywołało zdarzenie jest podmiotem (na przykład przycisk na formatce).
Kod źródłowy do opisanego wzorca znajduje się na githubie (znajduje się tam wersja z implementacją własnego wzorca obserwatora. Wersję z istniejącą implementacją wzorca w języku JAVA zostawiam jako ćwiczenia do wykonania samemu 😉 ).

Przykład uruchomienia aplikacji:

public class Startup {
    public static void main(String[] args) {
        OperatorAdam operatorAdam = new OperatorAdam();
        FirstAmbulance firstAmbulance = new FirstAmbulance(operatorAdam);
        SecondAmbulance secondAmbulance = new SecondAmbulance(operatorAdam);
        ThirdAmbulance thirdAmbulance = new ThirdAmbulance((operatorAdam));

        operatorAdam.setStatus("Car accident");
        operatorAdam.setStatus("Faint");
        operatorAdam.setStatus("Broken bone");
    }
}

Wyjście:

FirstAmbulance received a notification and is driving to incident right now!
Incident: Car accident
SecondAmbulance received a notification and is driving to incident right now!
Incident: Car accident
ThirdAmbulance received a notification and is driving to incident right now!
Incident: Car accident
FirstAmbulance received a notification and is driving to incident right now!
Incident: Faint
SecondAmbulance received a notification and is driving to incident right now!
Incident: Faint
ThirdAmbulance received a notification and is driving to incident right now!
Incident: Faint
FirstAmbulance received a notification and is driving to incident right now!
Incident: Broken bone
SecondAmbulance received a notification and is driving to incident right now!
Incident: Broken bone
ThirdAmbulance received a notification and is driving to incident right now!
Incident: Broken bone

Pozdrawiam i życzę sukcesów z wzorcami projektowymi 🙂