Java programming language logo

Observer Design Pattern

The Observer pattern defines one-to-many relationship between objects, and when the state of the one changes, the others get notified about the change automatically.

The one is usually called Subject or Observable, the others are called Observers. This pattern are also referred to as a publish-subscribe pattern.

In Observer pattern, two interfaces are defined. One for the Subject and an other for the Observers. The Subject defines methods for registering the listeners and notifying them about the changes. The Observer defines a method to update its state. This method is called by the Subject from its notify method.

The objects in this pattern are loosely coupled. They only know each other’s interface, nothing more.

Observer is a behavioral pattern.

Example

Let's see a Java example for the Observer pattern. The next example is about a soccer match. During the match the score is stored in a central database, which is represented by an object. This database is public and anyone can connect to it to get the current score. So there is one central database and many listeners (one-to-many). The central database is the Subject, the listeners are the Observers, those can be anything (mobile application, scoreboard, etc). When the score is changing, the central database notifies the listeners, so they update their states.

First, we create the Subject interface. The database object is going to implement this. It defines three methods. One for registering observers. For example, the mobile applications can connect to the database by this. There’s another method to remove observers. The third is called by the database when it changes its state and want to notify the observers about it.


package com.programcodex.designpatterns.observer;

public interface Subject {
    
    void registerObserver(Observer o);
    
    void removeObserver(Observer o);
    
    void notifyObservers();
}

The database do not register anything like apps, scoreboard, etc. It can only register observers, so if something wants to connect to the Subject, it has to implement the Observer interface. In this example, it defines one method, which will be called by the subject when it notifies the observer.


package com.programcodex.designpatterns.observer;

public interface Observer {

    void update(int goalsA, int goalsB);
}

By these two interfaces, the subject and the observers are loosely coupled. Subject knows nothing about observers and vice versa. They only know each other's interface and that’s all.

In the next step, we create the SoccerDatabase, which is the central database, and it implements the Subject interface. It stores the current scores in two integer fields. It also has to keep reference to the Observers, so it has a set for it.

When an observer wants to connect to the subject, it calls the registerObserver method. This method adds the observer to its set to keep the reference and sends its current state (scores) to the observer by the update method.

By the removeObserver method, an observer can be detached from the database.

When a team scores a goal (team A or team B for the sake of simplicity) they goal counter has to be incremented by the teamAScoreAGoal or teamBScoreAGoal method. After the incrementation, the notifyObservers method is called, which was defined in the Subject interface. This iterates through the observers and calls their update methods.


package com.programcodex.designpatterns.observer;

import java.util.HashSet;
import java.util.Set;

public class SoccerDatabase implements Subject {

    private Set<Observer> observers = new HashSet<>();

    private int teamAGoalCounter;
    private int teamBGoalCounter;

    public void teamAScoreAGoal() {
        this.teamAGoalCounter++;
        notifyObservers();
    }

    public void teamBScoreAGoal() {
        this.teamBGoalCounter++;
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer o) {
        this.observers.add(o);
        o.update(this.teamAGoalCounter, this.teamBGoalCounter);
    }

    @Override
    public void removeObserver(Observer o) {
        this.observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        observers.stream()
        .forEach(o -> o.update(this.teamAGoalCounter, this.teamBGoalCounter));
    }
}

Now let’s see an Observer implementation. It would be a mobile application for following the match. It’s called MatchWatcher. At instantiation, it gets a reference to the Subject and registers itself into it by the registerObserver method. It also gets a user name.

The update method receives the score and refreshes the display, so the user can see the changes.


package com.programcodex.designpatterns.observer;

public class MatchWatcherApp implements Observer {

    private String userName;

    private int goalsA;
    private int goalsB;

    private Subject soccerResultData;

    public MatchWatcherApp(String userName, Subject soccerResultData) {
        this.userName = userName;
        this.soccerResultData = soccerResultData;
        this.soccerResultData.registerObserver(this);
    }

    @Override
    public void update(int goalsA, int goalsB) {
        this.goalsA = goalsA;
        this.goalsB = goalsB;
        refreshDisplay();
    }

    private void refreshDisplay() {
        System.out.format("%s's app: Score - %d : %d%n",
                this.userName,this.goalsA, this.goalsB);
    }
}

Another Observer implementation is the Scoreboard, which is much simpler. It shows a different solution for the update method. This displays the score in a different way. After all a mobile device and a scoreboard have different displays.


package com.programcodex.designpatterns.observer;

public class Scoreboard implements Observer {

    private Subject soccerResultData;

    public Scoreboard(Subject soccerResultData) {
        this.soccerResultData = soccerResultData;
        this.soccerResultData.registerObserver(this);
    }

    @Override
    public void update(int goalsA, int goalsB) {
        System.out.println("---------");
        System.out.format("| %d : %d |%n", goalsA, goalsB);
        System.out.println("---------");
    }
}

And we are done. It’s time to run these things.

First, a SoccerDatabase, a Scoreboard and a MatchWatcherApp are instantiated. After that, the match begins and Team B scores a goal. Later, during the match, another app connects to the database and one is leaving.


package com.programcodex.designpatterns.observer;

public class TestObserver {

    public static void main(String[] args) {
        SoccerDatabase soccerResultData = new SoccerDatabase();
        Scoreboard scoreboard = new Scoreboard(soccerResultData);
        MatchWatcherApp johnApp = new MatchWatcherApp("John", soccerResultData);

        System.out.println("\n--- The match has just started, and there's a goal. ---");
        soccerResultData.teamBScoreAGoal();

        System.out.println("\n--- Another app has been started. ---");
        MatchWatcherApp maryApp = new MatchWatcherApp("Mary", soccerResultData);

        System.out.println("\n--- But Team A doesn't give it up...  ---");
        soccerResultData.teamAScoreAGoal();

        System.out.println("\n--- John is leaving. ---");
        soccerResultData.removeObserver(johnApp);

        System.out.println("\n--- ...and Team A scores another goal.  ---");
        soccerResultData.teamAScoreAGoal();
    }
}

The output of the above test class:

The Observer Design Pattern