Design Patterns - Observer Pattern
This would be the second in the list of many design patterns out there that I would like to talk about. I previously talked about the strategy pattern (which you should check out if you haven't already!) but if you haven't then this is a series of posts inspired by the Head First Design Pattern Series
Where is this applicable?
A common requirement while designing a program is for an object being able to observe changes in other objects. Theoretically the observer pattern can be defined as
The observer pattern is a one to many relationship from a subject to its subscribers. The subject maintains a list of subscribers and notifies them about changes in state of the subject.
A common tip in the object oriented world is to avoid tightly coupled classes. The observer pattern achieves this. What does coupling or rather loosely coupled mean in this context?
Loosely coupled systems are those where individual components have very little knowledge about the definitions and workings of other components.
The next question the may pop up is what is the benefit of having a loosely coupled system? Let's take the example of a simple application - a billing software. The designer of such a system will require the database to store new billing data when there's a new transaction and retrieve the billing data for reconciliation. In a tightly coupled system the programmer would have to know the exact workings of the database and the specific idiosyncrasies of the database in question.
This is a fairly straightforward way of designing a system but leaves little on the table when it comes to upgradibility. A switch in from one database to another would require a complete rewrite. But did the application ever need knowledge of how the database worked? No, all it wanted was being able to insert and fetch from the database, a fairly straightforward operation.
This is where a loosely coupled system shines. Since the front-end of the application doesn't know or care about how the database works as long as it fulfills certain predecided contracts we can switch it out easily. This makes the system extremely modular.
What does it entail?
At a high level, this pattern is like an entity running a mailing list and would have most of the features you would expect from a such a list. Programs implementing the observer pattern would typically have the following features:
- The subject maintains a list of observers or subscribers who are interested in the changes in the state of the subject.
- The subject offers a simple way to add a new subscriber to the list of subscribers
- The subject offers a way to unsubscribe from the list.
So let's begin with an example as always, let's continue from our last time's example of cars. A car may have many sensors, a framework to handle data from the sensors as displays to display said data. For the sake of this example let's assume there are three sensors - a temperature sensor, a voltage sensor and an oxygen sensor. As always we will program to an interface and begin by defining what rules classes will adhere to
public interface Subject {
public void newSubscriber(Subscriber object);
public void deleteSubscriber(Subscriber object);
public void notifySubscribers();
}
public interface Subscriber {
public void update(float temperature, float voltage, float oxygen);
}
public interface Display {
public void displayOnScreen();
}
As discussed above, we have three main interfaces will will program to
- The first interface is the subject, the main processor which will handle all the data coming from the sensors and has three features - a way to add and delete a subscriber from the list of subscribers and a way to notify all of them about a change in state.
- Subscribers are any entities that wish to stay up to date about any changes in state to the subject would be expected to implement this interface.
- Lastly a display interface, not all subscribers may display all the data and not all displays may be subscribing to the sensors.
Getting over to the actual classes, lets start with the CarData
class.
//imports have not been included!
public class CarData implements Subject {
private List<Subscriber> subscribers;
private float temperature;
private float voltage;
private float oxygen;
public CarData() {
subscribers = new ArrayList<Subscriber>();
}
public void newSubscriber(Subscriber object) {
subscribers.add(object);
}
public void deleteSubscriber(Subscriber object) {
subscribers.remove(object);
}
public void notifySubscribers() {
for (Subscriber subscriber : subscribers)
subscriber.update(temperature, voltage, oxygen);
}
public void measurementsChanged() {
notifySubscribers();
}
public void getSensorData() {
// unimplemented for brevity
measurementsChanged();
}
}
Initially we setup all the members of the class - the three floats to hold the temperature, voltage and oxygen levels respectively. We also create an ArrayList to store the list of subscribers. The constructor initializes the Arraylist. This is followed by the three methods we promised to implement in the interface. The method newSubscriber()
adds a new subscriber, deleteSubscriber()
removes an existing subscriber and notifySubscribers()
notifies all observers on the list.
The getSensorData()
is not implemented in the above program. It would be the place you can place the implementation for fetching data from the sensors of the car. After each fetch we also call the measurementsChanged()
method which notifies all the subscribers in the subscriber list.
Once we have implemented the Subject, we can implement the Subscriber
and Display
interfaces in the Dashboard
class.
public class Dashboard implements Subscriber, Display {
private float temperature;
private float voltage;
private float oxygen;
private CarData carData;
public Dashboard(CarData carData) {
this.carData = carData;
carData.newSubscriber(this);
}
public void update(float temperature, float voltage, float oxygen) {
this.temperature = temperature;
this.voltage = voltage;
this.oxygen = oxygen;
}
public void displayOnScreen() {
System.out.println("Temperature:" + temperature);
System.out.println("Voltage:" + voltage);
System.out.println("Oxygen:" + oxygen);
}
}
Again, each subscriber would maintain its own private copy of all the sensor data as well as a CarData
object. Unlike the subscriber, the observer does not need to maintain a list as this is a one to many relationship and the observer just needs to maintain a single link. We then implement the two methods update()
and displayOnScreen()
as defined in the interface.
With this we have successfully made use of the Observer Pattern.
Closing Thoughts
This concludes the second design pattern, the Observer Pattern. Last week, we covered the Strategy Pattern which we also made use of this week (Bonus Challenge: See if you can spot it anywhere in the above code!). Tentatively, this would be a twelve part series with the next instalment covering the Decorator Pattern, which should be out by the fourth of January.
As always I would love to hear your feedback on this post, feel free to write to me at akshay [at] akshayprabhu [dot] dev or tweet at my twitter handle @akshaprabhu200.