Simplify Object Syncing with the Observer Design Pattern

The Observer Design Pattern may not be the first thing that comes to mind for many Java coders, but implementing it can improve the scalability of your program.

Although my previous articles were about fullstack related issues such as MongoDB and Jax-RS i decided to move to some other space that I hope will be useful for my readers, since it relates with the design of large software systems. Hence, today I will discuss a design pattern called “The Observer”.

The Observer pattern is a powerful tool that enables an object (the subject) to notify other objects (the observers) when its state changes. This design pattern is particularly useful when you have multiple objects that need to be notified of changes in another object.

A basic use case for the Observer pattern is in a system that tracks the number of injuries, vaccine preventable diseases, and hospitalizations. For example, consider the Centers for Disease Control (CDC) in the United States. The CDC might have a system that tracks the number of injuries, vaccine preventable diseases, and hospitalizations in different states. This system would need to notify other objects (such as public health officials, hospitals, and insurance companies) when the numbers change.

To implement the Observer pattern in Java, we would start by defining the Subject interface. This interface would have methods for adding and removing observers, as well as a method for notifying the observers when the subject’s state changes. Here is an example of what the Subject interface might look like:

public interface Subject {
   void addObserver(Observer observer);
   void removeObserver(Observer observer);
   void notifyObservers();
}

Next, we would define the Observer interface. This interface would have a method for updating the observer’s state based on the subject’s state. Here is an example of what the Observer interface might look like:

public interface Observer {
   void update(int injuries, int vaccinePreventableDiseases, int hospitalizations);
}

Then, we would define a concrete implementation of the Subject interface, such as a DiseaseTracker class. This class would keep track of the number of injuries, vaccine preventable diseases, and hospitalizations, and would notify the observers when these numbers change. Here is an example of what the DiseaseTracker class might look like:

public class DiseaseTracker implements Subject {
  private List<Observer> observers;
  private int injuries;
  private int vaccinePreventableDiseases;
  private int hospitalizations;
  
  public DiseaseTracker() {
    this.observers = new ArrayList<>();
    this.injuries = 0;
    this.vaccinePreventableDiseases = 0;
    this.hospitalizations = 0;
  }
  
  public void setInjuries(int injuries) {
    this.injuries = injuries;
    notifyObservers();
  }
  
  public void setVaccinePreventableDiseases(int vaccinePreventableDiseases) {
    this.vaccinePreventableDiseases = vaccinePreventableDiseases;
    notifyObservers();
  }
  
  public void setHospitalizations(int hospitalizations) {
    this.hospitalizations = hospitalizations;
    notifyObservers();
  }
  
  @Override
  public void addObserver(Observer observer) {
    observers.add(observer);
  }
  
  @Override
  public void removeObserver(Observer observer) {
    observers.remove(observer);
  }
  
  @Override
  public void notifyObservers() {
    for (Observer observer : observers) {
      observer.update(injuries, vaccinePreventableDiseases, hospitalizations);
    }
  }
}

Finally, we would define a concrete implementation of the Observer interface, such as a PublicHealthOfficer class. This class would implement the update method from the Observer interface and would use the data provided by the DiseaseTracker to take action, such as issuing alerts or issuing recommendations. Here is an example of what the PublicHealthOfficer class might look like:

public class PublicHealthOfficer implements Observer {
  @Override
  public void update(int injuries, int vaccinePreventableDiseases, int hospitalizations) {
    if (injuries > 1000 || vaccinePreventableDiseases > 100 || hospitalizations > 1000) {
      System.out.println("ALERT: High numbers of injuries, vaccine preventable diseases, or hospitalizations detected. Issuing recommendations and alerts.");
    }
  }
}

To use the Observer pattern in our system, we would first create an instance of the DiseaseTracker class and an instance of the PublicHealthOfficer class. Then, we would add the PublicHealthOfficer instance as an observer of the DiseaseTracker instance. Finally, we would set the numbers for injuries, vaccine preventable diseases, and hospitalizations on the DiseaseTracker instance, which would trigger the PublicHealthOfficer instance to take action through the update method. Here is an example of how this might look in code:

DiseaseTracker tracker = new DiseaseTracker();

PublicHealthOfficer officer = new PublicHealthOfficer();

tracker.addObserver(officer);

tracker.setInjuries(1200);

tracker.setVaccinePreventableDiseases(150);

tracker.setHospitalizations(1100);

In this example, the PublicHealthOfficer instance would receive notifications from the DiseaseTracker instance when the numbers for injuries, vaccine preventable diseases, or hospitalizations change. If any of these numbers exceed a certain threshold, the PublicHealthOfficer instance would issue alerts and recommendations.

Pretty cool, ha?

The Observer pattern is a useful tool for implementing systems that need to notify multiple objects when something happens in another object. It allows for a decoupled of the objects, meaning that the objects do not need to know about each other in order to communicate. This can make it easier to add or remove objects from the system, as well as to modify the behavior of the system.

In addition, by decoupling objects and allowing them to communicate without needing to know about each other, the Observer pattern allows for easier modification and expansion of a system. This makes it a valuable tool for building scalable applications that can adapt and grow as needed. In the CDC example for instance, if a new object, such as a research institution, needs to be added to the system and receive notifications, it can simply be added as an observer. This allows for easy expansion of the system without the need to rewrite or redesign the existing objects.

If you enjoyed learning about the Observer pattern, you might also be interested in learning about other design patterns, such as the Factory pattern. The Factory pattern is a creational design pattern that helps to create objects in a super class, but allows subclasses to alter the type of objects that will be created. This can be useful in situations where you need to create different types of objects based on certain conditions. To learn more about the Factory pattern and other design patterns, you can explore online resources such as books, tutorials, and articles.

Leave a Reply

Your email address will not be published. Required fields are marked *