Obserwator

Wzorce projektowe

  |   9 min czytania

Zastosowanie

Obserwator (ang. Observer) (wzorzec behawioralny) służy do powiadamiania obiektów zainteresowanych - subskrybentów o zmianie stanu obiektu śledzonego - obserwowanego. Obiekty nasłuchujące mogą być zależne od stanu obiektu obserwowanego w związku z czym musi istnieć mechanizm komunikacji między nimi. Zmiana stanu może być utożsamiana ze zmianą wartości pól obiektu. Wzorzec Obserwator może być stosowany również do powiadamiania o stanie podjętej operacji poprzez wygenerowanie odpowiedniego zdarzenia. Obiekt może być obserwowany przez wielu subskrybentów, a także sam może być obserwatorem innych obiektów. Komunikaty nadawane przez obiekt obserwowany trafiają do wszystkich obserwatorów.

Ograniczenia

Obserwatorzy otrzymują komunikat z którego wiadomo, że coś się zmieniło, ale nie zawsze wiadomo co. Ponadto komunikat ten trafia do wszystkich subskrybentów, którzy niekoniecznie są zainteresowani daną zmianą stanu czy zdarzenia co wymusza na obserwatorach filtrowanie otrzymanego powiadomienia. Obserwatorzy nie wiedzą o istnieniu innych obserwatorów co może prowadzić do komplikacji. Jeśli obiekt obserwowany jest także obserwatorem wówczas istnieje ryzyko zapętetlęnia mechanizmu powiadamiania.

Użycie

Obserwator wykorzystywany jest tam, gdzie istnieje potrzeba informowania wielu obiektów w jednakowy sposób o zmianie stanu obiektu obserwowanego lub stanu podjętej operacji. Ze względu na ograniczenia wzorzec ten bywa przede wszystkim używany do informowania elementów systemu o wystąpieniu zdarzenia błędu czy też odpowiedzi sieciowej.

Implementacja

Obiekt obserwowany ConcreteObservable zawiera liste obserwatorów Observer oraz implementuje interfejs Observable za pomocą którego zapisuje, wypisuje i powiadamia obserwatorów o zmianie stanu. Obserwatorzy Observer1, Observer2 implementują interfejs Observer. Opcjonalny obiekt przekazywany jako parametr metody aktualizującej może pełnić podobną rolę jak zdarzenia w programowaniu reaktywnym, tzn.: określa rodzaj zdarzenia wraz z zestawem danych (argumenty opakowane w event).

Obserwator diagram

Poniższy listing przedstawia implementacje interfejsów obiektu obserwowanego Observable oraz jego obserwatorów Observer.

public class ConcreteObservable implements Observable {

    private List<Observer> observers;

    //other fields

    public ConcreteObservable() {
        observers = new ArrayList();
    }
	
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void unregisterObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for(Observer observer : observers)
            observer.update(this, null);
    }

    @Override
    public void notifyObservers(Object object) {
        for(Observer observer : observers)
            observer.update(this, object);
    }

    public void method1() {
        //do some work
        notifyObservers();
    }

    public void method2() {
        //do some work
        Event event = new Event(); //args wrapped in event
        notifyObservers(event);
    }

    //others methods
}

public class Observer1 implements Observer {
	
    @Override
    public void update(Observable observable, Object object) {
        if(observable instanceof ConcreteObservable) {
            //do some work
        }
        else {
            //do some work
        }
    }

    //other methods
}

public class Observer2 implements Observer {
	
    @Override
    public void update(Observable observable, Object object) {
        if(observable instanceof ConcreteObservable) {
            if(object instanceof Event) {
                //do some work
            }
        }
    }

    //other methods
}

public interface Observable {

    public void registerObserver(Observer observer);
    public void unregisterObserver(Observer observer);
    public void notifyObservers(Object args);
    public void notifyObservers();
}

public interface Observer {

    public void update(Observable observable, Object args);
}

Obserwatorzy zapisują się do subskrypcji obiektu klasy ConcreteObservable co pokazuje poniższy listing.

Observable observable = new ConcreteObservable();
Observer observer1 = new Observer1();
Observer observer2 = new Observer2();

observable.addObserver(observer1);
observable.addObserver(observer2);

//all Observers are notify
observable.method1();  //only observer1 do some update work 
observable.method2(); //only observer2 do some update work 

Przykład

Aplikacja aukcyjna AuctionHouse służy do licytowania oraz śledzenia wybranych aukcji w trybie rzeczywistym. Interfejs graficzny użytkownika przedstawia listę śledzonych aukcji Auctions, a także szczegółowy widok wybranej aukcji wraz z listą ofert Bids. Gdy pojawi się nowa aukcja lub oferta wówczas główny komponent systemu powiadamia o tym fakcie pozostałe moduły, które dokonują aktualizacji. Poniższy listing przedstawia komponent AuctionHouse, którego zadaniem jest komunikacja przetwarzanie otrzymanych komunikatów sieciowych.

public class AuctionHouse implements Observable {

    private List<Observer> observers;
    //other fields

    public AuctionHouse() {
        observers = new ArrayList();
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void unregisterObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for(Observer observer : observers)
            observer.update(this, null);
    }

    @Override
    public void notifyObservers(Object args) {
        for(Observer observer : observers)
            observer.update(this, args);
    }

    public void newBid(NewBidEvent event) {
        //check event details and add if is OK
        Bid bid = event.getBid();
        notifyObservers(bid);
    }

    public void newAuction(NewAuctionEvent auction) {
        //check event details and add if is OK
        Auction auction = event.getAuction();
        notifyObservers(auction);
    }

    //others methods
}

Moduły Auctions i Bids obserwują stan komponentu AuctionHouse oczekując na zmianę stanu aukcji.

public class Auctions implements Observer {

    private List<Auction> auctions;
    //other fields

    public Auction(List<Auction> auctions) {
    this.auctions = auctions;
    }

    @Override
    public void update(Observable observable, Object args) {
        if(observable instanceof AuctionManager) {
            if(args instanceof Auction) {
                Auction auction = (Auction) args;
                auctions.add(auction);
                //refresh UI list
            }
            else if(args == instanceof Bid) {
                Bid bid = (Bid) args;
                int auctionIndex = auctions.indexOf(bid.getAuction());
                if(auctionIndex != -1) {
                    auctions.get(auctionIndex).addBid(bid);
                    //refresh UI list
                }
            }
        }
    }

    //other methods
}

public class Bids implements Observer {

    private Auction auction;
    private List<Bid> bids;
    //other fields

    public Bids(Auction auction) {
        this.auction = auction;
        this.bids = auction.getBids();
    }

    @Override
    public void update(Observable observable, Object args) {
        if(observable instanceof AuctionManager) {
            if(args instanceof Bid) {
                Bid bid = (Bid) args;
                if(bid.getAuction() == auction) {
                    bids.add(bid);
                    //refresh UI list
                }
            }
        }
    }

    //other methods
}

Rejestracja modułów obserwatorów do głównego komponentu systemu oraz ich aktualizacja przedstawia się następująco.

ActionHouse house = new ActionHouse();
Auctions auctions = new Auctions();
Bids bids = new Bids();

//register
house.registerObserver(auctions);
house.registerBids(bids);

//new action coming from server and runs ActionHouse
house.newBid(bidEvent); //update Bids and Auctions modules
house.newAuction(auctionEvent); //update Auctions module

Biblioteki

Wzorzec Obserwator w implementacji klas Observer i Observable należał do standardowej biblioteki Java, jednakże ze względu na wspomniane zagrożenia oraz brak zapewnienia poprawnego działania w środowisku wielowątkowym, został uznany w Java 9 jako deprecated. Zalecena jest korzystanie z klas PropertyChangeListener i PropertyChangeEvent lub reaktywnych strumieni z pakietu Flow. Popularnymi bibliotekami wzorca Obserwator w Androidzie są szyny zdarzeń EventBus i Otto, a także RxAndroid powstały na bazie RxJava, który jest implementacją podejścia do programowania reaktywnego.