Observer Design Pattern with Java

Do you remember how to use the Swing API with action events? If not, refresh your memory here:

       
button.addActionListener(new ActionExample());

public class ActionExample implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        //code that reacts to the action...
    }
}

When using the Swing API you also use the Observer pattern maybe even without realizing it. The Observer pattern enables us to decouple the observed actions and isolate each process that must be executed. It’s clear that we will gain flexibility and cohesion.

observer_diagram.PNG

Get the Design_Patterns_Saga_GitHub_Source_Code

1 – Observer: It’s the generic abstract class that enables us to implement the Observer pattern. We will use the most powerful feature of Object Oriented programming, the Polymorphism. It’s crucial to fully master Polymorphism when using the Design Patterns.

public abstract class Observer {
    protected Subject subject;
    abstract void update();
}

2 – Subject: It orchestrates the necessary actions that must be executed when using the Observers. The Subject class contains the generic methods to be reused at the specific Subject class.

public abstract class Subject {

    private List<Observer> observers = new ArrayList<>();

    abstract void setState(String state);
    abstract String getState();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

public abstract class Subject {

    private List<Observer> observers = new ArrayList<>();

    abstract void setState(String state);
    abstract String getState();

    public void attach(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

3 – Subject specialization: In this class, we are defining the specific actions to be executed by any Observer class.

public class MessageStream extends Subject {

    private Deque<String> messageHistory = new ArrayDeque<>();

    @Override
    void setState(String message) {
        messageHistory.add(message);
        this.notifyObservers();
    }

    @Override
    String getState() {
        return messageHistory.getLast();
    }
}

4 – Observers specializations: Finally the Observers’ implementations! Basically, to make everything work we add the subject in the Observers‘ constructors and invoke the addMessage method.

public class SlackClient extends Observer {

    public SlackClient(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }

    public void addMessage(String message) {
        subject.setState(message + " - sent from Slack");
    }

    @Override
    void update() {
        System.out.println("Slack Stream: " + subject.getState());
    }

}

public class WhatsAppClient extends Observer {

    public WhatsAppClient(Subject subject) {
        this.subject = subject;
        subject.attach(this);
    }

    public void addMessage(String message) {
        subject.setState(message + " - sent from WhatsApp");
    }

    @Override
    void update() {
        System.out.println("WhatsApp Stream: " + subject.getState());
    }
}

5 – Unit Tests: Let’s see how it works. The use of the Observer pattern is pretty easy. Basically, we will instantiate the Subject specialization and pass it into the Observer specialization constructor. Then we will invoke the addMessage method and it’s done!

public class ObserverTest {

    @Test
    public void observerTest() {
        Subject subject = new MessageStream();

        WhatsAppClient whatsAppClient = new WhatsAppClient(subject);
        SlackClient slackClient = new SlackClient(subject);

        slackClient.addMessage("Another new message!");
        Assert.assertEquals("Another new message! " +
                "- sent from Slack", subject.getState());

        whatsAppClient.addMessage("Here is a new message!");
        Assert.assertEquals("Here is a new message! " +
                "- sent from WhatsApp", subject.getState());
    }

}

Summary of actions:

  1. Created the Observer abstract class
  2. Declared the Subject as a field inside the Observer abstract class
  3. Created the Subject abstract class
  4. Created the Subject specialization class
  5. Created the Observer specialization class
  6. Instantiated the Subject specialization class
  7. Created Observer specialization instances
  8. Passed the Subject instance to the Observer constructor
  9. Invoked the addMessage method from the Observers’ specialization

To practice the Observer pattern you can create another Subject or Observer. You could create a FileStreamSubject and InstagramClient, for example. It’s crucial to practice. Create at least one example and practice your programming skills using TDD!

Written by
Rafael del Nero
Join the discussion