Observer Design Pattern

Observer Design Pattern

Documenting my system design learning process

What is an Observer Design Pattern?

We all have heard of this design pattern or have implemented it without knowing about it if you are learning software development or have been working on any code base. In the software world, different design patterns are available for you to implement. In simple terms, the super intelligent people of the software world have classified these design patterns into a use-case basis system that way it is more straightforward for novices like me to find a suitable pattern for use.

Similarly, this design pattern falls under the category of behavioural design patterns. It allows you to develop a subscription-based mechanism that tracks and notifies you about any changes in the designated objects.


Why do we need this?

let's take an age-old example of a Weather Monitoring System. Suppose there is a system that tracks the current temperature of a region and you have your electronic devices like laptops and mobiles that are programmed to notify you if there is any change in the temperature. Now, in a world where these IT geniuses have not found or conceptualized the Observer Design pattern, how this implementation will look?

Solving a Problem without the Observer Pattern

This can be achieved in two ways -

Work is done by your mobile/ laptop system :

In this implementation, the mobile/ laptop has to send an API request each second to get the current temperature from the weather monitoring system to keep track of the current temperature and also have to keep a store of the previous temperature to know if the temperature has actually changed or not. Without going further into this approach we can say there are n-numbers of issues with this.
(Brighter and more Experienced minds of this field can expand upon this, but my knowledge and will to type both are kind of limited right now)

Work is done by the Weather Monitoring System :

Now let's take the opposite approach. Hypothetically let's say the weather monitoring system has to send a notification to the devices when there's a change in the temperature.

But send a notification to whom?

Let's say there are two devices and one is requesting notification when there's a change in the temperature and the other one is requesting a rain alert. How will the Weather Monitoring System will know and store all of this data? So it will end up sending false information to the devices.

How to solve this using the Observer Design pattern?

In short, we can implement a class that can observe the change in temperature and notifies the devices. Basically, the observable class will have the data of the device that has requested an update and also keep track of the data itself. When there is any movement it'll call the device and trigger the "notify" functionality.


Solving a practical Question (Including Code snippet)

Let's take a question where we have to design a "Notify Me" mechanism that is very common in any E-Commerce website.
We have to notify the customer the customer when the stock of the required product has been replenished. As in real life there can be multiple customers (observers) observing multiple items(observable).

Creating interfaces for the observer and observable that can be implemented by any number of concrete classes

package oberserverPatttern.interfaces;

public interface ObserverInterface {
    void update();
}
package oberserverPatttern.interfaces;

public interface ObservableInterface {

    void addObserver(ObserverInterface obj);
    void removeObserver(ObserverInterface obj);
    void notifyObserver();
    void setNewStock(int newStock);
}

Now let's implement these interface

We are only implementing a single concrete class for both the mobile and laptop stock that will observe any change in the stock of both. We are doing this for the sake of simplicity. In real life, if there are 'n' products there ideally should be 'n' concrete implementation.

We are taking a list of observer objects to store the observers who have requested the notification. In the notify method we are iterating through the list of observers and then call the update() that is defined in the observer interface.

The important point here is how we are calling the notify() method.

public class StockObservableImpl implements ObservableInterface{
    private int stockCount = 0;
    private List<ObserverInterface> observerList = new ArrayList<>();

    //setNewStock() will only trigger the notify if the curr count is zero
    @Override
    public void setNewStock(int newStock) {
        if(stockCount == 0) notifyObserver();
        stockCount += newStock;
    }

    @Override
    public void addObserver(ObserverInterface obj) {
        observerList.add(obj);

    }

    @Override
    public void removeObserver(ObserverInterface obj) {
        observerList.remove(obj);

    }

    @Override
    public void notifyObserver() {
        // TODO Auto-generated method stub
        for(ObserverInterface observer : observerList) {
            observer.update();
        }    
    }
}

Implementation of Mobile observer

public class MobileObserver implements ObserverInterface {
    private String name;
    private ObservableInterface interfaceObj;

    public MobileObserver(ObservableInterface obj, String name) {
        this.interfaceObj = obj;
        this.name = name;
    }

    @Override
    public void update() {
        System.out.println("Mobiles are back in stock Mr. "+ name);

    }
}

One thing that is very important here is how we are finding the right observable for this observer. If there are multiple observable classes, we had to use the instanceof operator inside the update() to call the right business logic.
But we are passing the object ObservableInterface in the constructor of the observer class. That way when we are initializing the object, it will know which implementation class to call.

Implementation of Laptop observer

Both implementations are somewhat similar

public class LaptopObserver implements ObserverInterface {

    private String name;
    private ObservableInterface interfaceObj;

    public LaptopObserver(ObservableInterface obj, String name) {
        this.setInterfaceObj(obj);
        this.name = name;
    }

    @Override
    public void update() {
        System.out.println("Laptops are again in stock, Mr. "+ name);
    }

    public ObservableInterface getInterfaceObj() {
        return interfaceObj;
    }

    public void setInterfaceObj(ObservableInterface interfaceObj) {
        this.interfaceObj = interfaceObj;
    }
}

Now let's check the main method

public class Main {
    public static void main(String[] args) {
        System.out.println("in Main");
        ObservableInterface observable = new StockObservableImpl();
        ObserverInterface laptopObserver = new LaptopObserver( observable,       "Sinchan Saha");
        ObserverInterface mobileObserver = new MobileObserver(observable,         "John Doe");
        observable.addObserver(mobileObserver);
        observable.addObserver(laptopObserver);
        //should be notfied as curr stock is 0
        observable.setNewStock(5);
        //there should not be any notification
        observable.setNewStock(10);
        //setting stock as zero again
        observable.setNewStock(-15);
        //should be called again
        observable.setNewStock(10);
    }
}

Output

Mobiles are back in stock Mr. John Doe
Laptops are again in stock, Mr. Sinchan Saha

Mobiles are back in stock Mr. John Doe
Laptops are again in stock, Mr. Sinchan Saha

Credits

I would love to credit Coding && Concept Youtube Channel, Christopher Okhravi's Youtube Channel and Head First Design Patterns book.