Introduction
In this article, we'll be implementing the Observer Design Pattern to solve a commonly occurring problem in object-oriented software development.
Design Patterns are standardized solutions to common problems in the software development industry. Being familiar with them, a developer is able to recognize where each should be implemented and how it would help solve a particular design issue.
Early design disaster prevention can save a huge amount of time and cost for a team trying to push out a product.
Behavioral Design Patterns
Behavioral Design Patterns provide responsibility assignment between instances of classes. Also, they define types of relationships and communication between objects.
The main idea is to achieve some expected behavior of an application and create a flexible design at the same time.
Observer Design Pattern
The Observer Design Pattern is a way to design a subsystem that allows many objects to respond automatically to changes of a particular object that's being "observed".
It addresses the decomposition of an Observable
and Observer
s - or a publisher and subscribers.
For an Observable
object, we use the term Subject. Objects that are subscribed to the Subject's changes are called Observers. A Subject and Observers are typically in one-to-many dependency.
The Observer Design Pattern is also known as the Event-Subscriber or the Listener pattern.
Note: Java has an official implementation of the Observer Design Pattern and it's the backbone of JMS (Java Message Service). It's generally used for building even-driven applications, though, the official implementation isn't really wide-spread and many people implement the pattern according to their own use-cases.
Motivation
Probably the most well-known example is a button listener that perform an action upon clicking the button. This pattern, in general, is pretty common in Java GUI components. It is a way to react to events happening with visual objects.
As a social media user, you might be following some people. We can say that you are an observer of your friend's social media feed (Subject of observation) and you get notifications about their new posts and life events. Interestingly, your friend is an Observer of your feed as well.
Let's add more complexity and say that you probably have several or even hundreds of different observers and they can react differently to your posts. It is possible that one object can be a subject of observation and an observer of another subject. They can even have this relationship between themselves.
As a more real-world example - a fire alarm in a mall has to notify all stores that a fire is going on. These stores are observing the fire alarm signal and reacting to its changes.
As you can see, the problem is pretty widespread and oftentimes it isn't trivial to solve with other designs.
Implementation
Let's assume that a store chain wants to notify their loyal customers of an ongoing sale. The system would send a short message to all subscribed customers whenever a sale has been activated.
In this case, our store is the subject of observation, and our customers are observing it. Let's define the Subject
and Observer
interfaces for our objects to implement:
public interface Subject {
public void addSubscriber(Observer observer);
public void removeSubscriber(Observer observer);
public void notifySubscribers();
}
The Subject
interface is pretty straightforward. It provides methods to add and remove subscribers/observers and notify them of a change.
The Observer
interface is even simpler:
public interface Observer {
public void update(String message);
}
The only thing an Observer
really needs is to know when there's an update from their subject. Their behavior based on this update will differ between classes.
With our interfaces out the way, let's implement the Subject
interface through a store:
public class Store implements Subject {
private List<Observer> customers = new ArrayList<>();
@Override
public void addSubscriber(Observer customer) {
customers.add(customer);
}
@Override
public void removeSubscriber(Observer customer) {
customers.remove(customer);
}
@Override
public void notifySubscribers() {
System.out.println("A new item is on sale! Act fast before it sells out!");
for(Observer customer: customers) {
customer.update("Sale!");
}
}
}
The store contains a list of observers (customers) and implements the methods for the addition and removal of customers from the list.
The notifySubscribers()
method simply loops through the list of them and sends them an update.
We can have as many Observer
implementations as we'd like. It's only natural for people to react differently to a sale. A shopaholic will likely jump in rejoice while a passive customer will likely make note of the sale and remember it for later.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Let's go ahead and implement these two types of customers:
public class ShopaholicCustomer implements Observer {
@Override
public void update(String message) {
processMessage(message);
}
private void processMessage(String message) {
System.out.println("Shopaholic customer is interested in buying the product on sale!");
// A complex psychologic response to a sale by a shopaholic
}
}
public class PassiveCustomer implements Observer {
@Override
public void update(String message) {
System.out.println("Passive customer made note of the sale.");
// Passive customer does not react to the message too much
}
}
And finally, let's take a look at the Observer Design Pattern in action by activating a sale in a shop that's being watched by a few of customers:
public static void main(String[] args) {
// Initialization
Subject fashionChainStores = new ChainStores();
Observer customer1 = new PassiveCustomer();
Observer customer2 = new ShopaholicCustomer();
Observer customer3 = new ShopaholicCustomer();
// Adding two customers to the newsletter
fashionChainStores.addSubscriber(customer1);
fashionChainStores.addSubscriber(customer2);
// Notifying customers (observers)
fashionChainStores.notifySubscribers();
// A customer has decided not to continue following the newsletter
fashionChainStores.removeSubscriber(customer1);
// customer2 told customer3 that a sale is going on
fashionChainStores.addSubscriber(customer3);
// Notifying the updated list of customers
fashionChainStores.notifySubscribers();
}
And running this piece of code will yield:
A new item is on sale! Act fast before it sells out!
Passive customer made note of the sale.
Shopaholic customer is interested in buying the product on sale!
A new item is on sale! Act fast before it sells out!
Shopaholic customer is interested in buying the product on sale!
Shopaholic customer is interested in buying the product on sale!
Changing the state of the store results in the changed state of the subscribed customers. This same principle would apply to a fire alarm or a news feed. As soon as someone posts a post, all of the observers are notified and take some action depending on their responsibility/interest.
We can modify the list of a subject's observers at any point. Also, we can add any implementation of the Observer
interface. This gives us the ability to build a robust event-driven system that pushes updates to observers and updates the whole system based on the changes in a single particular object.
Pros and Cons
The Observer Design Pattern is a great contribution to the support of the Open/Close Design Principle. It helps us build designs with high cohesion but loose coupling.
In other words, the Observer and the Subject have a strictly specified mission. The Subject updates an Observer with some information and doesn't know about the Observer's implementation. This characteristic gives us flexibility.
This pattern allows us to add and remove Observers at any time. You don't need to modify either Subject nor Observer for it.
There is an issue in the Observer Design Pattern though.
The order of notifications is not under our control. There is no priority among subscribers during notification.
This means that if an Observer's execution is dependent on another Observer's execution beforehand, there's no guarantee that these two will execute in that order.
However, it is valuable to understand that a pattern is a high-level description of a particular solution. When we apply a pattern to two different applications, our code will be different. We can sort our Observers and they will get a notification in an expected order. This is not a feature of the Observer Design Pattern, but it is something we can do.
Conclusion
When you know design patterns, some complex problems can be boiled down to proven simple solutions.
The Observer Design Pattern is really useful in event-driven systems where many objects may depend on a state of another object. If implemented poorly, this will result in a coupled and stiff application where it's really hard to test objects individually and updating code will be a hassle.
In this article, we've explored how we can solve this issue and make a flexible, decoupled solution that your team will be thankful for.