Whether you're sending party invitations, receiving a news alert on your phone, or waiting for that weekly email subscription for "dogs of the week", you're acting in part of a widely used design pattern known as the Observer pattern.

Assumptions

What is the Observer Pattern?

An observer is an object that receives updates from another object known as a subject. A subject is able to send updates out to many observers much like a weather station is able to send out text and email alerts to people who have subscribed to receive them.

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/ObserverPattern_01.png" alt="observer pattern UML diagram" style="max-width:95%;"> </div>

Key components:

  • Subject: an interface (or class) that notifies a collection of observers of any new changes in state.
  • Observer: an interface defining an update method that the concretions can use to be notified.
  • Concrete Observer: implementations of the Observer that individually choose how to handle the updated data.

> The observer pattern is also often referred to as the subscriber pattern!

The observer pattern is powerful because it is able to notify any object that wants to listen without needing to know its underlying implementation. This works by the observers all implementing an Observer interface with an update method for the subject to call upon.

Conceptualization

Houses that can be controlled from a remote were once a thing of science fiction, however in the last decade, they have ventured into reality, and now we can control our lights, electronics, and thermostat from a smartphone. Imagine that a setup had a "master switch" with the ability to turn off and on all the devices registered on it; when it is pressed, it sends and update out to each device telling it to set itself to the desired state.

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/ObserverPattern_02.png" alt="smart-switch observer UML diagram" style="max-width:95%;"> </div>

Starting with our subject, the MasterSwitch, we'll want to create it in such a way that observers can query the state of the switch. Additionally, the master switch will need a list of every device observing it:

// Subject class:
class MasterSwitch {
   List&lt;ElectricalObject&gt; observers = new List&lt;ElectricalObject&gt;();
   boolean state;
   
   // Updates all of the observers:
   void notifyObservers() {
     for(ElectricalObject observer: observers) {
       observer.update();
     }
   }
   // Adds a new device to the list:
   void addObserver(Observer o) {
     items.add(o);
   }
   // Controller for turning all the devices on and off:
   void flip(boolean state) {
     this.state = state;
     notifyObservers();
   }
   
   boolean getState() {
     return state;
   }
 }

With our master switch set up, we can move forward with defining our Observer interface. It will be very simple, only needing an update() method. Additionally, we'll define an abstract class ElectricalObject that all our devices will inherit. Abstract classes are not necessary in the observer pattern, however in this instance we're using it as it saves us a lot of repetitive coding:

interface Observer {
   void update();
 }

// Abstract class implementing observer:
abstract class ElectricalObject implements Observer {
   MasterSwitch subject;
   boolean isOn;
   
   ElectricalObject(MasterSwitch subject) {
     this.subject = subject;
     subject.addObserver(this);
   }
   
   void update() {
     setSwitch(subject.getState());
   }
   
   abstract void setSwitch(boolean state); 
 }

One thing to note about this design is that the update() method takes no parameters, so our Electrical objects will need a way to retrieve the state from the subject; this is usually solved by the observers having a reference to the subject as well.

Alternative approaches include using casting to pass a generic object through the update method, and only the observers who are looking for a specific subject will use it!

Now that we have an abstract class defined, let's move onto creating some concrete electrical objects

// Concrete observers:
class CeilingFan extends ElectricalObject {

   TV(MasterSwitch subject) {
     super(subject);
   }
   
      void setSwitch(boolean state) {
     if(isOn != state) {
       isOn = state;
       print("Fan turned " + (isOn? "on" : "off"));
     }
   }
 }

class Lamp extends ElectricalObject {

   Lamp(MasterSwitch subject) {
     super(subject);
   }
 
   setSwitch(boolean state) {
      if(isOn != state) {
       isOn = state;
       print("Lamp turned " + (isOn? "on" : "off"));
     }
   }
 }

class TV extends ElectricalObject {
   
   TV(MasterSwitch subject) {
     super(subject);
   }
   
   setSwitch(boolean state) {
       if(isOn != state) {
         isOn = state;
       print("TV turned " + (isOn? "on" : "off"));
     }
   }
 }

Client Code:

static void main(String[] args) {
   // Create the master switch:
   MasterSwitch masterSwitch = new MasterSwitch();
   // Create some electrical items:
   TV tv = new TV(masterSwitch);
   Lamp lamp = new Lamp(masterSwitch);
   CeilingFan fan = new CeilingFan(masterSwitch);
   // Turn all on:
   masterSwitch.flip(true);
   // Turn all off:
   masterSwitch.flip(false); 
 }

> Challenge: implement multiple master switches that can be used for different groups of objects!

Output:

TV turned on
Lamp turned on
Fan turned on
Tv turned off
Lamp turned off
Fan turned off