Wzorce projektowe w programowaniu – wzorzec Obserwator

Ten wpis ma na celu przybliżenie funkcjonowania wzorca Obserwator. Jest on używany, w celu informowania obiektów o zmianie stanu jednego obiektu. Spójrzmy na poniższy rysunek.

Idea wzorca Obserwator

Wyjaśniając powyższą ilustrację – jeżeli nastąpi zmiana danych w obiekcie obserwowanym, to nowe wartości są przekazywane do obiektów obserwujących. Z tego z kolei wynika, że obiekty obserwujące są zależne od obserwowanego i mogą być automatycznie aktualizowane. Występuje tutaj relacja jeden do wielu.

Obiekt obserwowany (Podmiot – bo tak inaczej się go nazywa) i obserwujący (nazywany po prostu Obserwatorem) nie wiedzą o sobie zbyt wiele. jedyną informacją, jaką posiada obiekt obserwowany jest to, że obserwator posiada pewien określony interfejs (interfejs Obserwator). Zobaczmy bardziej praktyczny na poniższym diagramie.

Diagram klas przedstawiający ideę wzorca Obserwator

Omówmy sobie teraz po kolei każdy z elementów diagramu.
Interfejs Podmiot – obiekty wykorzystują go do rejestrowania się w charakterze obserwatorów oraz do usunięcia swoich danych z listy obserwatorów.
ObiektPodmiot – dany obiekt obserwowany zawsze musi posiadać zaimplementowany interfejs Podmiot. Oprócz metod pozwalających na zarejestrowanie i usunięcie obserwatora z listy, obiekt obserwowany ma także możliwość poinformowania obserwatorów o zmianie swojego stanu (poprzez metodę powiadomObserwatorow(). Oczywiście dany obiekt może także posiadać metody związane z ustawianiem i pobieraniem jego stanu (pobierzStan() oraz ustawStan()).
Interfejs Obserwator – wszystkie obiekty będące potencjalnymi obserwatorami muszą implementować interfejs Obserwator. Ten interfejs posiada tylko jedną metodę aktualizacja(), która jest wywoływana, kiedy Podmiot zmieni swój stan.
Obiekt Obserwator – poszczególne obiekty obserwujące mogą być obiektami dowolnej klasy, która posiada zaimplementowany interfejs Obserwator. Aby otrzymywać kolejne powiadomienia, każdy obiekt obserwujący musi się zarejestrować u danego obiektu obserwowanego

Nowych obserwatorów można dodawać w dowolnym momencie. Ponieważ jedynym elementem na którym opiera się działanie obiektu obserwowanego jest lista zarejestrowanych obiektów implementujących interfejs Obserwator, nowe obiekty obserwujące mogą być dodawane w dowolnym momencie działania programu. Możemy wymienić dowolny, zarejestrowany obiekt obserwujący na inny, usunąć go lub dodać nowy. Obiekt obserwowany niezależnie od tego będzie funkcjonował tak samo, jak do tej pory.

Pożyteczną rzeczą jest fakt, że dodawanie nowych typów obiektów obserwujących nigdy nie pociąga za sobą konieczności modyfikacji obiektu obserwowanego. Jeżeli, dajmy na to, mamy aplikację do której chcemy dodać dodatkową funkcjonalność. Pojawi się nowa klasa, której obiekty muszą być obserwatorami. Wtedy nie trzeba zmieniać kodu obiektu obserwowanego. Należy tylko w nowym obiekcie (który ma być obserwatorem) zaimplementować interfejs Obserwator, zarejestrować nowego obserwatora na liście obiektu obserwowanego. Obiekt obserwowany nie będzie się przejmował – jego zadaniem jest po prostu dostarczanie określonych informacji do wszystkich obserwatorów.

Zarówno obiekty obserwowane, jak i obiekty obserwujące mogą być niezależnie od siebie wielokrotnie wykorzystywane. Zależności są, mówiąc bardzo ogólnie, luźne. Więc jeżeli dla któregoś z obiektów obserwowanych lub obserwujących znajdzie się inne niż dotychczas zastosowanie, można je wykorzystać. Takie luźne relacje są bardzo pomocne podczas tworzenia elastycznych systemów, które muszą często adaptować się do zachodzących zmian w ich funkcjonalności.

Zarówno zmiany wprowadzone do obiektu obserwowanego, jak i do obiektów obserwujących, nie mają na siebie żadnego wpływu. Ze względu na to, że podmiot i obserwator są ze sobą luźno powiązani, można niemal dowolnie te dwa rodzaje obiektów modyfikować, póki poszczególne obiekty będą spełniały założenie posiadania poprawnie zaimplementowanego interfejsu Podmiot lub Obserwator.

Teorię najlepiej zobrazować na przykładzie, także zacznijmy. Załóżmy, że pracujemy w firmie zajmującej się skupywaniem minerałów i materiałów (złoto, srebro, ropa naftowa, gaz ziemny itp.), by potem odsprzedawać je z zyskiem. Musimy być na bieżąco z wszelkimi zmianami ich cen, prognozami tychże cen dla poszczególnych materiałów a także ich historią na przestrzeni określonego czasu (by np. móc wysnuć wnioski z wcześniejszych zmian i tendencji zmian cen). Aby wspomóc się w otrzymywaniu informacji i nie musieć przeklikiwać stron internetowych w celu ciągłego śledzenia zmian cen zlecono stworzenie aplikacji, która wyświetlała by te dane i informowała, kiedy doszło do zmiany ceny jednego z materiałów. Dane te powinny być wyświetlane w trzech formatach: aktualnie, średnia (wyświetla średnią cenę materiałów na podstawie cen z przeszłości) oraz prognoza. Aplikacja jest po części już stworzona – posiada zaimplementowane API zbierające ceny z wyszukanych wczesjodpowiednią klasę CenyDane (służy do tworzenia obiektów obsługujących API), pewne metody oraz . Dostarczono nam podstawowe elementy:
– API zbierające ceny z wyszukanych wcześniej źródeł,
– klasę CenyDane służącą do tworzenia obiektów obsługujących wspomniane API.
Plan aplikacji przedstawiono poniżej.

Podstawowy plan aplikacji obserwacji kursów materiałów

Głównymi elementami aplikacji są:
– API zbierające wszystkie ceny materiałów z wynalezionych źródeł (nie zajmujemy się tutaj aspektem pobierania tych informacji, załóżmy, że takie API zostało po prostu już przez kogoś stworzone jako OpenSource),
– obiekt Ceny zapewniający śledzenie danych nadchodzących z API i aktualizuje informacje wyświetlane na ekranie (przyjmijmy także, że obiekt ten wie, w jaki sposób komunikować się z API),
– interfejs aplikacji, którego zadaniem jest zobrazowanie informacji o bieżących cenach materiałów.

Jak wspomniałem, została nam dostarczona klasa CenyDane.

Metoda cenyZmiana() jest wywoływana za każdym razem, gdy pojawią się nowe dane związane ze zmianą cen poszczególnych materiałów (nie dbamy o to, w jaki sposób jest wywoływana – mamy tylko wykorzystać ją do zobrazowania danych).

Podsumowując, naszym małym zadaniem jest implementacja metody cenyZmiana(), tak, aby była ona w stanie automatycznie aktualizować informacje wyświetlane w trybach: aktualnie, średnia oraz prognoza.

Zwróćmy jeszcze uwagę na to, że samo zaimplementowanie wspomnianej powyżej funkcjonalności nie jest trudne, jednak ważne jest, aby zapewnić maksymalną elastyczność. W późniejszym czasie zostaną stworzone nowe tryby wyświetlania danych, a poszczególni użytkownicy aplikacji powinni mieć możliwość usuwania lub dodawania wybranych trybów wyświetlania.

Wykonajmy pierwszą próbę z pisaniem kodu.

public class CenyDane() {
// deklaracja zmiennych
     public void cenyZmiana() {
          double zloto = pobierzCeneZlota();
          double srebro = pobierzCeneSrebra();
          double gaz = pobierzCeneGazu();
          double ropa = pobierzCeneRopy();

          aktualnieWyswietl.aktualizacja(zloto, srebro, gaz, ropa);
          sredniaWyswietl.aktualizacja(zloto, srebro, gaz, ropa);
          prognozaWyswietl.aktualizacja(zloto, srebro, gaz, ropa);
     }
}

Może i powyższy fragment nie miałby problemów z samym działaniem, jednak występują tu pewne problemy natury optymalizacyjnej. W przypadku tworzenia poszczególnych implementacji (aktualnieWyswietl() itd.) nie ma żadnych możliwości dodawania ani usuwania wyświetlanych elementów bez ingerencji w kod programu co może prowadzić do narastających problemów związanych z rozwijaniem aplikacji.

Aby problem rozwiązać, należałoby skorzystać z tematu niniejszego wpisu – wzorca Obserwator. Stwórzmy jeszcze raz diagram klas aplikacji.

Wyjaśnijmy, co obrazuje powyższy diagram. Obiekt CenyDane posiada zaimplementowany interfejs Podmiot. Wszystkie obiekty odpowiedzialne za przetwarzanie parametrów cen materiałów mają zaimplementowany interfejs Obserwator. Takie rozwiązanie udostępnia obiektowi obserwowanemu Podmiot wspólny interfejs, który pozwala na przesyłanie aktualizowanych na bieżąco informacji do wszystkich podległych obiektów obserwujących. Utworzony został także wspólny interfejs, który posiadają wszystkie obiekty odpowiedzialne za wyświetlanie informacji na ekranie. Możliwe jest także wykorzystywanie interfejsów Podmiot i Obserwator do tworzenia własnych trybów wyświetlania.

Skoro mamy już na czym się opierać, zaimplementujmy teraz poglądową aplikację to śledzenia i wyświetlania cen śledzonych przez nas materiałów.

public interface Subject {

    public void registerObserver(Observer o);
    public void deleteObserver(Observer o);
    public void callObservers();
}

public interface Observer {
    
    public void actualization (double gold, double silver, double gas, double 
    petroleum);
}

public interface ShowElement {

    public void schow();
}

Przeanalizujmy stopniowo kod. Interfejs Podmiot (Subject) posiada metody zarejestruj oraz usuń, które pobierają jako argument obiekt typu Observator (Observer), czyli konkretnie obiekt, który ma zostać zarejestrowany na liście zarejestrowanych lub usunięty z tej listy. Obecna jest także metoda powiadamiająca, która wywoływana jest w celu powiadomienia wszystkich obserwatorów o tym, ze stan obiektu obserwowanego zmienił się.

Idąc dalej, wszystkie obiekty mają zaimplementowany interfejs Obserwator, więc wszystkie muszą mieć także zaimplementowaną metodę actualization() – tutaj przekazujemy wyniki kolejnych cen do poszczególnych obserwatorów. Zmienne przekazywane jako argumenty do metody actualization() odpowiadają wartościom stanu, jakie obiekty obserwujące otrzymują od obiektu obserwowanego, kiedy zmieniają się ceny śledzonych przez nas materiałów.

Interfejs PokażElement posiada jedną metodę, która wywoływana będzie w sytuacji, kiedy niezbędne będzie wyświetlanie danego elementu na ekranie.

Mamy podstawę, idźmy dalej. parę akapitów wyżej implementowaliśmy klasę CenyDane. Teraz czas trochę ją przeprojektować na potrzeby wykorzystywanego wzorca Obserwator.

public class PriceData implements Subject {

    private ArrayList observers;
    private double gold;
    private double silver;
    private double gas;
    private double petroleum;

    public PriceData() {
        observers = new ArrayList();
    }
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    public void deleteObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }
    public void callObservers() {
        for (int i = 0; i < observers.size(); i++) {
            Observer Obs = (Observer)observers.get(i);
            Obs.actualization(gold, silver, gas, petroleum);
        }
    }
    public void pricesChange() {
        callObservers();
    }
    public void setPrices(double gold, double silver, double gas, double petroleum) {
        this.gold = gold;
        this.silver = silver;
        this.gas = gas;
        this.petroleum = petroleum;
        pricesChange();
    }
}

Wyjaśnijmy wprowadzone zmiany. Klasa CenyDane (PriceData) implementuje teraz interfejs Podmiot. Dodano zmienną typu ArrayList, której zadaniem jest przechowywanie listy obserwatorów. Kiedy przyszły obserwator się rejestruje, dopisujemy go do końca listy (registerObserver()); podobnie w przypadku usuwania obserwatora (deleteObserver()). Metoda callObservers() to miejsce, gdzie powiadamiani są obserwatorzy, że zmienił się stan obiektu obserwowanego. jako, ze wszystkie obiekty mają zaimplementowany interfejs Obserwator oraz metodę actualization() wiemy, jak możemy je powiadomić. Powiadamianie obserwatorów będzie uruchamiane w momencie zmiany wartości cen (pricesChange() – pamiętajmy, że nie interesuje nas, w jaki sposób metoda pricesChange() jest wywoływana – to zostało już wykonane poza nami).

Nadszedł czas na stworzenie elementów, które będą wyświetlały odpowiednie informacje w poszczególnych trybach pracy na ekranie (zaimplementujemy tylko tryb „aktualnie”, jednak pozostałe tryby implementujemy analogicznie według potrzeb).

public class ActuallyShow implements Observer, ShowElement {

    private double gold;
    private double silver;
    private double gas;
    private double petroleum;
    private Subject PriceData;

    public ActuallyShow(Subject PriceData) {
        this.PriceData = PriceData;
        PriceData.registerObserver(this);
    }

    public void actualization(double gold, double silver, double gas, double petroleum) {

        this.gold = gold;
        this.silver = silver;
        this.gas = gas;
        this.petroleum = petroleum;
        show();
    }

    public void show() {

        System.out.println("Obecna cena  złota = " + gold);
        System.out.println("Obecna cena  srebra = " + silver);
        System.out.println("Obecna cena  gazu = " + gas);
        System.out.println("Obecna cena  ropy naftowej = " + petroleum);
    }
}

Mamy już wszystko. Przygotujmy środowisko testowe.

public class PriceMaterialsTest {

    public static void main(String[] args) {

        PriceData priceData = new PriceData();

        ActuallyShow actuallyShow = new ActuallyShow(priceData);

        priceData.setPrices(10.1, 7.3, 6.7, 6.5); //symulacja zmiany stanu
        priceData.setPrices(11.8, 8.1, 4.3, 3.8); //symulacja zmiany stanu
    }
}

W odpowiedzi dostajemy następującą odpowiedź.

Zbudowany przez nas projekt wykorzystujący wzorzec Obserwator działa. Podczas zmiany stanu obiektu głównego (obserwowanego) została o tym wysłana informacja do wszystkich obiektów obserwujących (w naszym przypadku tylko obiekt klasy ActuallyShow), które miały za zadanie wyświetlanie niezbędnych, przypisanych do nich zestawów informacji.

Wzorzec Obserwator w języku Java

Java posiada własną obsługę tego wzorca. Najbardziej ogólną implementacją jest interfejs Observer i klasa Observable (oba elementy zlokalizowane w pakiecie java.util). Dzięki temu pakietowi możliwe jest korzystanie zarówno z metody wysyłania powiadomień przez obiekt obserwowany, jak i z metody pobierania stanu obiektu obserwowanego przez obiekty obserwujące. Aby lepiej zrozumieć interfejs java.util.Observer i klasę java.util.Observable spójrzmy na poniższy diagram.

Observable – jest to klasa a nie interfejs, więc klasa CenyDane dziedziczy po klasie Observable. Śledzi ona wszystkie obiekty obserwujące i wysyła do nich powiadomienia.
CenyDane – nasz obiekt obserwowany. Nie potrzebujemy już implementować metod dodajObserwatora(), usunObserwatora() lub powaidomObserwatorow() – opisane zachowania dziedziczymy po klasie Observable.
Observer – interfejs, który w zasadzie jest taki sam jak w poprzednim diagramie klas. Implementujemy go we wszystkich klasach będących obserwatorami.
Obiekty obserwujące – nadal mamy jeden wspólny interfejs, który posiada metodę update() wywoływaną przez oiekt obserwowany.

Aby dowolny obiekt został obserwatorem, należy zaimplementować interfejs Observer i wywołać metodę addObserver() dla wybranego obiektu klasy Observable. Analogicznie, aby usunąć obiekt z listy obserwatorów, konieczne jest wywołanie metody deleteObserver().

Aby wybrany obiekt klasy Observable wysyłał powiadomienia o zmianie stanu musi on dać się obserwować, czyli dziedziczyć po klasie nadrzędnej java.util.Observable. Oprócz tego należy wywołać metodę setChanged(),a by zasygnalizować, że zmienił się stan obiektu obserwowanego. Następnie należy wywołać metodę notifyOPbservers().

Innym wyjściem jest pobieranie danych przez obiekty obserwujące przy pomocy metody update(Observable o, Object arg). Argumentem Observable o jest obiekt, który wysyła powiadomienie. Natomiast argumentem Object arg jest obiekt danych, który został wcześniej przekazany do metody notifyObservers() (lub też wartość null, jeżeli taki obiekt nie został wcześniej określony).

W klasie Observable widzimy metodę setChanged(). Na wcześniejszym diagramie (w klasie Podmiot) podobnej metody nie było. W przypadku pakietu java.util metoda ta jest wykorzystywana do zasygnalizowania, że dany obiekt zmienił stan i wywołanie metody notifyObservers() powinno przynieść aktualizację danych dla obserwatorów. Wywołanie metody notifyObservers() bez uprzedniego użycia setChanges() nie przyniesie zamierzonego skutku – obserwatorzy nie zostaną zaktualizowani. Spójrzmy na kod poniżej.

setChanged() {
     changed = true
}
notifyObservers(Object arg){
     if (changed) {
          "dla każdego obserwatora na liście" {
          call update(this, arg)
          }
          changed = false
     }
}
notifyObservers(){
     notifyObservers(null)
}

Po co takie rozwiązanie? Metoda setChanged() została pomyślana w celu zapobieżenia zbyt częstego wysyłania powiadomień do obserwatorów. Korzystając z przykładu naszej aplikacji – jeżeli API zbierające zmiany cen materiałów jest „bardzo czułe” i podaje cenę z dokładnością tysięcznych części złotówki, to może się okazać, że zmiany wartości będą odbywać się bardzo często (zależy to oczywiście od mechanizmów rządzących rynkiem), a nam taka informacja niekoniecznie jest potrzebna.

Zastosowanie java.util w naszej aplikacji

Najpierw dopasujmy kod klasy CenyDane (PriceData) tak, aby można było skorzystać z klasy Observable.

import java.util.Observable;
import java.util.Observer;

public class PriceData extends Observable {

    private double gold;
    private double silver;
    private double gas;
    private double petroleum;

    public PriceData() {}

    public void pricesChange() {

        setChanged();
        notifyObservers();
    }
    public void setPrices(double gold, double silver, double gas, double petroleum) {
        this.gold = gold;
        this.silver = silver;
        this.gas = gas;
        this.petroleum = petroleum;
        pricesChange();
    }

    public double getGoldPrice() {
        return gold;
    }

    public double getSilverPrice() {
        return silver;
    }

    public double getGasPrice() {
        return gas;
    }

    public double getPetroleumPrice() {
        return petroleum;
    }
}

Zamieniliśmy implementację interfejsu Podmiot dla klasy obiektu obserwowanego na dziedziczenie po klasie Observable. Nie musimy także dłużej śledzić naszych obserwatorów ani zarządzać procesami ich rejestracji i usuwania z listy (usunięto listę tablic oraz cały kod przeznaczony dodawania i usuwania obserwatorów oraz wysyłania powiadomień) – tym zajmuje się teraz klasa nadrzędna Konstruktor nie musi teraz tworzyć osobnej struktury danych, która była przeznaczona do przechowywania obserwatorów. Dodanych na końcu getterów obserwatorzy będą używać do pobierania informacji o stanie obiektu klasy PriceData.

Zmodyfikujmy teraz klasę ActuallyShow.

import java.util.Observable;
import java.util.Observer;

public class ActuallyShow implements Observer, ShowElement {

    Observable observable;
    private double gold;
    private double silver;
    private double gas;
    private double petroleum;

    public ActuallyShow(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    public void update(Observable obs, Object arg) {

        if (obs instanceof PriceData) {

            PriceData priceData = (PriceData)obs;
            this.gold = priceData.getGoldPrice();
            this.silver = priceData.getSilverPrice();
            this.gas = priceData.getGasPrice();
            this.petroleum = priceData.getPetroleumPrice();
            show();
        }
    }

    public void show() {

        System.out.println("Obecna cena  złota = " + gold);
        System.out.println("Obecna cena  srebra = " + silver);
        System.out.println("Obecna cena  gazu = " + gas);
        System.out.println("Obecna cena  ropy naftowej = " + petroleum);
    }
}

Pokrótce wyjaśnijmy co widzimy powyżej. Konstruktor naszej klasy pobiera obecnie obiekt klasy Observable, a my wykorzystujemy to do wpisania obiektu opisującego warunki bieżące na listę obserwatorów. Ponadto, zmodyfikowana została metoda update() tak, aby mogła pobierać jako argumenty zarówno obiekt klasy Observable jak i opcjonalny obiekt danych. W przypadku tej metody najpierw musimy upewnić się, że obiekt obserwowany jest typu CenyData (PriceData), a następnie możemy wykorzystać jego metody służące do pobierania danych do uzyskania wartości cen.

Skompilujmy nas kod i sprawdźmy, czy działa prawidłowo.

Wady java.util.Observable

Implementacja klasy java.util. Observable obciążona jest szeregiem problemów, które ograniczają użyteczność i możliwość zastosowania.
Pierwszym z nich jest fakt, że Observable jest klasą. Z tego powodu, aby z niej skorzystać, należy stworzyć jej klasy podrzędne. Oznacza to, że nie można dodać zachowań klasy Observable do innej klasy już istniejącej klasy, która dziedziczy po innej klasie nadrzędnej. To znacznie ogranicza możliwość wielokrotnego wykorzystywania tej klasy. Dodatkowo, ponieważ nie istnieje interfejs Observable, nie można zbudować swojej własnej implementacji, która będzie dobrze współpracowała z wbudowanym API interfejsu Observer języka Java.
Drugim problemem jest to, że klasa Observable chroni swoje kluczowe metody. Jeżeli przyjrzymy się API wzorca Observer, przekonamy się, że metoda setChanged() jest chroniona. Oznacza to, że nie możemy wywołać tej metody dopóki nie będziemy dziedziczyć po tej superklasie. Nie możemy nawet stworzyć obiektu klasy Observable i wyposażyć go w swoje własne metody – nie ma innej możliwości od dziedziczenia.

Inne miejsca

Pakiety java.util.Observer oraz java.util.Observable to nie jedyne miejsca, gdzie można znaleźć implementację wzorca Obserwator. Zarówno komponenty JavaBeans jak i Swing mają własne jego implementacje.
W bibliotece Swing napotykamy na tzw. słuchaczy (ang. listeners) – czyli obserwatorów. Idąc dalej, jeden z elementów Swing API, JButton, posiada wiele metod pozwalających na dodawanie i suswanie słuchaczy, śledzących obiekt nasłuchiwany. Przykładowo, Action Listener pozwala na „nasłuchowanie” dowolnego typu zdarzeń, jakie mogą wystąpić w związku z przyciskiem (np. naciśnięcie przycisku). W Swing API można naleźć wiele różnych rodzajów obiektów nasłuchujących.

Dla zobrazowania napiszmy program, który będzie zawierać 1 przycisk. Gdy zostanie on kliknięty, słuchacze (obserwatorzy) mają wykonać jakąś akcję. Zaimplementujemy trzy takie obiekty, które wyświetlą na ekranie określony komunikat: Pozytywny („Super, że kliknąłeś!”), Negatywny („Mówiłem, żebyś nie klikał!”) i Neutralny („Obojętne mi to.”).

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SwingObserverExample {
    JFrame frame;
    public static void main(String[] args) {
        SwingObserverExample przykład = new SwingObserverExample();
        przykład.uruchom();
    }
    public void uruchom() {
        frame = new JFrame();
        JButton button = new JButton("Czy powinienem kliknąć?");
        button.addActionListener(new PositiveObserver());
        button.addActionListener(new NegativeObserver());
        button.addActionListener(new NeutralObserver());
        frame.getContentPane().add(BorderLayout.CENTER, button);
// Parametry okienka
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(BorderLayout.CENTER, button);
        frame.setSize(300,300);
        frame.setVisible(true);
    }
    class PositiveObserver implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("Super, że kliknąłeś!");
        }
    }
    class NegativeObserver implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("Mówiłem, żebyś nie klikał!");
        }
    }
    
    class NeutralObserver implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("Obojętne mi to.");
        }
    }
}

Polecenie button.addActionListener powoduje, że przekazywany jako argument, nowo tworzony obiekt, staje się słuchaczem zdarzeń związanych z przyciskiem.
Kiedy zmienia się stan obiektu obserwowanego, w obiektach obserwujących zamiast metody update() wywoływana jest metoda actionPerformed().
Zauważmy, że klasy obserwatorów zostały zdefiniowane jako klasy wewnętrzne klasy SwingObserverExample, jednak tak wcale być nie musi.

Definicja wzorca Obserwator

Na koniec, skoro już wiemy czym charakteryzuje się wzorzec Obserwator i jak się go używa, można przytoczyć jego regułę.

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