Observer pattern in Java and Python

Behavioral Design Patterns provide solution for the better interaction between objects and how to provide lose coupling and flexibility to extend easily. Following design patterns come under this category.

Observer pattern defines a dependency between objects so that whenever an object changes its state, all its dependents are notified.

Observer pattern is used when there is one-to-many relationship between objects such as if one object is modified, its depenedent objects are to be notified automatically.

In observer design pattern multiple observer objects registers with a subject for change notification. When the state of subject changes, it notifies the observers.

Objects that listen or watch for change are called observers and the object that is being watched for is called subject.

You can think of observer design pattern in two ways

  • Subject-Observer relationship. Object which is being observed is refereed as Subject and classes which observe subject are called Observer.
  • Publisher-Subscriber relationship. A publisher is one who publish data and notifies it to the list of subscribers who have subscribed for the same to that publisher. A simple example is Newspaper. Whenever a new edition is published by the publisher, it will be circulated among subscribers whom have subscribed to publisher.

Key points of observer design pattern are

  • Subject provides interface for observers to register and unregister themselves with the subject.
  • Subject knows who its subscribers are.
  • Multiple observers can subscribe for notifications.
  • Subject publishes the notifications.
  • Subject just sends the notification saying the state has changed. It does not pass any state information.
  • Once the notification is received from subject, observers call the subject and get data that is changed.

Lets' take the example of a news agency. A news agency gather news news and publish them to different subscribers. We need to create a framework for and agency to be able to inform immediately, when event occurs, its subscribers about the event. The subscribers can receive the news in different ways: Emails, SMS, etc. The solution need to be extensively enough to support new types of subscribers(maybe new communication technologies will appear).

dp_observer.gif

There are four participants in the Observer pattern

  • Subject, which is used to register observers. Objects use this interface to register as observers and also to remove themselves from being observers.
  • Observer defines an updating interface for objects that should be notified of changes in a subject. All observers need to implement the Observer interface. This interface has a method update(), which gets called when the Subject's state changes.
  • ConcreteSubject, stores the state of interest to ConcreteObserver objects. It sends a notification to its observers when its state changes. A concrete subject always implements the Subject interface. The notifyObservers() method is used to update all the current observers whenever the state changes.
  • ConcreateObserver maintains a reference to a ConcreteSubject object and implements the Observer interface. Each observer registers with a concrete subject to receive updates.

The following sequence diagram illustrates the registration and notification flow in action.

dp_observer_seq.png

When to use the Observer pattern

  • When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
  • When a change to one object requires changing others, and you don’t know how many objects need to be changed?
  • When an object should be able to notify other objects without making assumptions about who these objects are. In other words, you don’t want these objects tightly coupled.

The Observer pattern is usually used in combination with other design patterns:

  • Factory pattern. It's very likely to use the factory pattern to create the Observers so no changes will be required even in the main framework. The new observers can be added directly in the configuration files.
  • Template Method. The observer pattern can be used in conjunction with the Template Method Pattern to make sure that Subject state is self-consistent before notification.
  • Mediator Pattern. The mediator pattern can be used when we have cases of complex cases of many subjects an many observers.

Now we'll look at an example of the observer pattern. We'll start by creating an interface for the subject called WeatherSubject. This will declare three methods: addObserver(), removeObserver(), and doNotify().

Java

File WeatherSubject.java.

public interface WeatherSubject {
    public void addObserver(WeatherObserver weatherObserver);
    public void removeObserver(WeatherObserver weatherObserver);
    public void doNotify();
}

We'll also create an interface for the observers called WeatherObserver. It features one method, a doUpdate() method.

public interface WeatherObserver {
    public void doUpdate(int temperature);
}

The WeatherStation class implements WeatherSubject. It is our subject class. It maintains a set of WeatherObservers which are added via addObserver() and removed via removeObserver(). When WeatherSubject's state changes via setTemperature(), the doNotify() method is called, which contacts all the WeatherObservers with the temperature via their doUpdate() methods.

File WeatherStation.java.

public class WeatherStation implements WeatherSubject {
    Set<WeatherObserver> weatherObservers;
    int temperature;

    public WeatherStation(int temperature) {
        weatherObservers = new HashSet<WeatherObserver>();
        this.temperature = temperature;
    }

    @Override
    public void addObserver(WeatherObserver weatherObserver) {
        weatherObservers.add(weatherObserver);
    }

    @Override
    public void removeObserver(WeatherObserver weatherObserver) {
        weatherObservers.remove(weatherObserver);
    }

    @Override
    public void doNotify() {
        Iterator<WeatherObserver> it = weatherObservers.iterator();
        while (it.hasNext()) {
            WeatherObserver weatherObserver = it.next();
            weatherObserver.doUpdate(temperature);
        }
    }

    public void setTemperature(int newTemperature) {
        System.out.println("\nWeather station setting temperature to " + newTemperature);
        temperature = newTemperature;
        doNotify();
    }
}

WeatherCustomer1 is an observer that implements WeatherObserver. Its doUpdate() method gets the current temperature from the WeatherStation and displays it.

File WeatherCustomer1.java.

public class WeatherCustomer1 implements WeatherObserver {
    @Override
    public void doUpdate(int temperature) {
        System.out.println("Weather customer 1 just found out the temperature is:" + temperature);
    }
}

WeatherCustomer2 performs similar functionality as WeatherCustomer1.

File WeatherCustomer2.java.

public class WeatherCustomer2 implements WeatherObserver {
    @Override
    public void doUpdate(int temperature) {
        System.out.println("Weather customer 2 just found out the temperature is:" + temperature);
    }
}

The Demo class demonstrates the observer pattern. It creates a WeatherStation and then a WeatherCustomer1 and a WeatherCustomer2. The two customers are added as observers to the weather station. Then the setTemperature() method of the weather station is called. This changes the state of the weather station and the customers are notified of this temperature update. Next, the WeatherCustomer1 object is removed from the station's collection of observers. Then, the setTemperature() method is called again. This results in the notification of the WeatherCustomer2 object.

public class Demo {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation(33);

        WeatherCustomer1 wc1 = new WeatherCustomer1();
        WeatherCustomer2 wc2 = new WeatherCustomer2();
        weatherStation.addObserver(wc1);
        weatherStation.addObserver(wc2);

        weatherStation.setTemperature(34);
        weatherStation.removeObserver(wc1);
        weatherStation.setTemperature(35);
    }
}

Python 3

import abc

class WeatherSubject(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def add_observer(self, weather_observer):
        pass

    @abc.abstractmethod
    def remove_observer(self, weather_observer):
        pass

    @abc.abstractmethod
    def do_notify(self):
        pass


class WeatherObserver(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def do_update(self, temperature):
        pass


class WeatherStation(WeatherSubject):
    def __init__(self, temperature):
        self.observers = []
        self.temperature = temperature

    def add_observer(self, weather):
        self.observers.append(weather)

    def remove_observer(self, weather):
        self.observers.remove(weather)

    def do_notify(self):
        for observer in self.observers:
            observer.do_update(self.temperature)

    def set_temperature(self, temperature):
        print("Weather station setting temperature to %s" % temperature)
        self.temperature = temperature
        self.do_notify()


class WeatherCustomer1(WeatherObserver):
    def do_update(self, temperature):
        print("Weather customer 1 just found out the temperature is: %s" % temperature)


class WeatherCustomer2(WeatherObserver):
    def do_update(self, temperature):
        print("Weather customer 2 just found out the temperature is: %s" % temperature)


if __name__ == '__main__':
    station = WeatherStation(33)

    wc1 = WeatherCustomer1()
    wc2 = WeatherCustomer2()
    station.add_observer(wc1)
    station.add_observer(wc2)

    station.set_temperature(34)
    station.remove_observer(wc1)
    station.set_temperature(35)
comments powered by Disqus