Observer Pattern - Java - Explained

complete guide with its intent, motivation, scenario based code-example.

Observer Pattern - Java - Explained

Intent

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
This is also known as "dependents", "Publisher-Subscriber" and "Subject - Observer" pattern. It's a behavioral design pattern that helps to implement a sort of subscription mechanism where if the object changes its state all notifiers are notified about the change.

Publisher+Subscriber = Observer pattern
Subject+Observer= Observer pattern
Observable+ Observer = Observer pattern.

Motivation behind the pattern / problem it solves?

  1. When we want to notify other objects (observers) about any change in the specific object (observable) it's best to use the observer pattern.
  2. Observer pattern promotes the loose coupling between objects.
  3. when changes to the state of one object may require to change other objects, and the actual set of objects to notify must change dynamically.

some scenarios where we can think to use the observer pattern

  1. inStock notification: If the product is out of stock and we have provided button "Notify me when in stock" button, so when the customer will click on this button that customer is registered for the products stock update and when stock will be updated it's expected to notify relevant customers.

  2. newsletter subscriptions: Customers can subscribe to multiple newsletters by selecting checkboxes, and as soon as any new news is updated it's expected to notify relevant customers.

design problem :

Problem statement .

In Stock product :
Once product stock is updated notify the list of customers who signed to get an update when the product will be updated with the latest stock.

Solution : (Along with code example)

structure strategy pattern

image.png

Click here to open img in new tab : ObserverPatternImg.jpg

Refer below for get basic idea over code, get complete github code reference here.

*** Main 
public class NotifyMeStockUpdateMain {

    public static void main(String[] args) {

        // create some products
        Product product1 = new Product("Product1",34,true);
        Product product2 = new Product("Product2",23,false);
        Product product3 = new Product("Product3", 12,false);
        Product product4 = new Product("product4", 12,false);
        Product product5 = new Product("product5", 18,false);

        // create some customers
        Customer customer1 = new Customer(123,"cusotmer1");
        Customer customer2 = new Customer(124,"cusotmer2");
        Customer customer3 = new Customer(125,"cusotmer3");
        Customer customer4 = new Customer(126,"cusotmer4");
        Customer customer5 = new Customer(127,"cusotmer5");

        // register customers for the product2
        product2.registerCustomer(customer1);
        product2.registerCustomer(customer2);
        product2.registerCustomer(customer3);

        // external service calls postUpdateAboutStock method to update stock for the product2.
        product2.postUpdateAboutStock(true);

        // removing customer 2 from product2 inStock update list
        product2.removeCustomer(customer2);

        // external service calls postUpdateAboutStock method to update stock for the product2.
        // notice customer 2 is not posted with the product 2 inStock update
        product2.postUpdateAboutStock(true);

        // Customer 5 wish to check inStock for product 5 by himself
        customer5.setProductWatch(product5);
        boolean isInStock = customer5.observableProduct.getUpdateAboutStock(customer5);
        System.out.println("Customer 5 checks for product5 instock and product in stock is "+isInStock);


    }
}

***  Concrete subject
public class Product implements ObservableProduct {

    String name;
    double price;
    boolean inStock;
    List<ObserverCustomer> customersList;

    public Product(String name, double price, boolean inStock) {
        this.name = name;
        this.price = price;
        this.inStock = inStock;
        this.customersList = new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public boolean isInStock() {
        return inStock;
    }

    public void setInStock(boolean inStock) {
        this.inStock = inStock;
    }

    @Override
    public boolean registerCustomer(ObserverCustomer customer) {
        if (null != customer && !customersList.contains(customer)) {
            customersList.add(customer);
            return true;
        }
        return false;
    }

    @Override
    public boolean removeCustomer(ObserverCustomer customer) {

        if (null != customer && customersList.contains(customer)) {
            customersList.remove(customer);
            System.out.println(" removed customer from "+this.name+ "products inStock notification list");
            return true;
        }
        return false;
    }

    @Override
    public void notifyCustomersAboutProductInStock() {
        for(ObserverCustomer observer : customersList) {
            observer.update(this);
        }
    }

    @Override
    public boolean getUpdateAboutStock(ObserverCustomer observerCustomer) {
        return this.inStock;
    }

    @Override
    public String getProductDetails() {
        return this.name;
    }

    public void postUpdateAboutStock(boolean inStock) {
        this.inStock = inStock;
        if (this.inStock) {
            System.out.println("Stock is updated please notify respective customer");
            notifyCustomersAboutProductInStock();
        }
    }
}

*** concrete observers
public class Customer implements ObserverCustomer {

    double custID;
    String customerName;
    ObservableProduct observableProduct;

    public Customer(double custID, String customerName) {
        this.custID = custID;
        this.customerName = customerName;
    }

    @Override
    public void update(ObservableProduct observableProduct) {
        boolean productInStockUpdate = observableProduct.getUpdateAboutStock(this);

        if(productInStockUpdate == false) {
            System.out.println("Product stock has not been updated");
        } else {
            System.out.println("Hello "+this.customerName+" there is new stock available for the product : "+observableProduct.getProductDetails());
        }
    }

    @Override
    public void setProductWatch(ObservableProduct product) {
        this.observableProduct = product;
    }
}

** *Output 
Stock is updated please notify respective customer
Hello cusotmer1 there is new stock available for the product : Product2
Hello cusotmer2 there is new stock available for the product : Product2
Hello cusotmer3 there is new stock available for the product : Product2
 removed customer from Product2products inStock notification list
Stock is updated please notify respective customer
Hello cusotmer1 there is new stock available for the product : Product2
Hello cusotmer3 there is new stock available for the product : Product2
Customer 5 checks for product5 instock and product in stock is false

Publisher (ObservableProduct)

This is also known as a Subject which knows about the observers, When there is a change in the state of the subject it notifies the list of observers, in our above structure its notifyCustomersAboutProductInStock this method is being called when there is a change in the stock.
We can have multiple concrete implementation of the Publisher

Subscriber (ObserverCustomer)

This is also known as Observer, this register itself with the subject and exposes an update method which is used by the Publisher to update updated state of publisher.

In Our case update(ObservableProduct) is called to update in stock status, and setProductWatch is used to pull status on ad-hoc basic from Publisher.
We can have a concrete implementation of subscribers .

Pros and Cons

Pros

  1. We can introduce new subscribers without changing publisher code , which helps to achieve open/close principle .
  2. Loose coupling between dependent objects are achieved with observer pattern

Cons :

subscribers may experience unexpected update if object composition is not maintainable .

Relation with other patterns

Command vs Observer

Command establish unidirectional connection between objects however in observer we can create by-directional connection . In our example , update method in observer is to update publisher state to observer however getUpdateAboutStock method in publisher is for subscriber to get state of publisher on request.

complete github code