Design Patterns — Observer
Intro
In this article, we will discuss a Behavioral Design Pattern known as the Observer. We will also go over an example of this pattern in Java. Before we begin, let’s take a look at what a Design Pattern is, and why you should familiarize yourself with them.
Design Patterns
A Design Pattern is not a programming language, or a specific type of technology you can use. It is a paradigm, or, a pre-thought out solution to common problems you will encounter when building projects. Many scenarios you encounter when coding have probably already been encountered before. Instead of trying to re-invent the wheel, why not just utilize a solution that has already proven itself many times over? This is why you should learn Design Patterns on your coding journey.
There are many Design Patterns, and multiple different groups of Design Patterns. There are also many resources available that can aide you in learning these common solutions as well. You can also check out my previous articles here, where I have discussed other Patterns, as well as algorithms and other useful coding topics!
Observer Design Pattern
The Observer Design Pattern is a Behavioral pattern that allows some objects to notify other objects about a change in state. It essentially creates a one-to-many relationship between objects so that when an object changes state, all of the objects that depend on it are notified and automatically updated.
This pattern can usually be identified if you see a subscription method that stores an incoming list of objects. You may also see a notification method that iterates over a list of objects and calls on their “update” method.
There are 3 key components to the Observer Design Pattern — The Observer, Subject, and the Client.
The Subject contains a list of Observers to notify when it changes its state, so it should define methods that allow the Observers to register and unregister themselves. It should also contain a method to notify the Observers and it can either send the update while notifying, or can provide another method to get the update.
The Observer should have a method that sets the object it will be watching, and another method that will be used by the Subject to notify them of any updates.
Example
In our example we will be subscribing to a Product, and the Observers can register to this Product. Whenever an update on the Product is made, all the registered Observers will be notified and take the message.
First we will create our Subject that defines the contract methods to be implemented by any concrete Subject.
public interface Subject {
// register and unregister observers
public void register(Observer obj);
public void unregister(Observer obj);
// notify observers of change
public void notifyObservers();
// get updates from subject
public Object getUpdate(Observer obj);
}
Next we will create our Observer. It will contain a method to attach the Subject to the Observer and another method to be used by the Subject to notify of any change.
public interface Observer {
// update the observer, used by subject
public void update();
//attach with subject to observe
public void setSubject(Subject sub);
}
Now, lets create our Subject example, Product.
import java.util.ArrayList;
import java.util.List;
public class Product implements Subject {
private List<Observer> observers;
private String message;
private boolean changed;
private final Object MUTEX= new Object();
public Product() {
this.observers = new ArrayList<>();
}
@Override
public void register(Observer obj) {
if (obj == null) throw new NullPointerException("Null Observer");
synchronized (MUTEX) {
if (!observers.contains(obj)) observers.add(obj);
}
}
@Override
public void unregister(Observer obj) {
synchronized (MUTEX) {
observers.remove(obj);
}
}
@Override
public void notifyObservers() {
List<Observer> observersLocal = null;
//synchronization is used to make sure any observer registered after message is received is not notified
synchronized (MUTEX) {
if (!changed)
return;
observersLocal = new ArrayList<>(this.observers);
this.changed = false;
}
for (Observer obj : observersLocal) {
obj.update();
}
}
@Override
public Object getUpdate(Observer obj) {
return this.message;
}
// post message to Product
public void postMessage(String msg) {
System.out.println("Posted Message: " + msg);
this.message = msg;
this.changed = true;
notifyObservers();
}
}
The register and unregister methods simply take an obj as an argument, and either adds it to the observers list, or removes it from the observers list. postMessage() will be used by the Client application to post a String msg about the Product. The changed boolean keeps track of when the state is changed and is referenced when notifying the observers. This boolean value is useful because if the notifyObservers() method is called and there is no update, then it wont send any false notifications. synchronized is used in notifyObservers() to make sure the notification is only sent to the registered observers before the message is published to the Product.
Next let’s create our Observer example, ProductSubscriber.
public class ProductSubscriber implements Observer {
private String name;
private Subject product;
public ProductSubscriber(String n) {
this.name = n;
}
@Override
public void update() {
String msg = (String) product.getUpdate(this);
if (msg == null) {
System.out.println(name + ": no new message!");
} else {
System.out.println(name + " - Receiving Message: " + msg);
}
}
@Override
public void setSubject(Subject sub) {
this.product = sub;
}
}
The update() method calls the Subject getUpdate() method to get the message it’s meant to receive.
Now that we’ve got our Observer written out, let’s see it in action!
public class ObserverTest {
public static void main(String[] args) {
// create the subject
Product product = new Product();
// create the observers
Observer obj1 = new ProductSubscriber("John");
Observer obj2 = new ProductSubscriber("David");
Observer obj3 = new ProductSubscriber("Steven");
// register the observers to the subject
product.register(obj1);
product.register(obj2);
product.register(obj3);
// attach the observer to the subject
obj1.setSubject(product);
obj2.setSubject(product);
obj3.setSubject(product);
// check if any update is available
obj1.update();
// send message to subject
product.postMessage("New Product Update!");
}
}
When checking out the console, you can see that just by posting the message, each object is notified and logs to the console. We can also see that only obj1 is called when checking for an update. This is the power of the Observer!
obj1.update();
// John: no new message!
product.postMessage("New Product Update!");
// Posted Message: New Product Update!
// John - Receiving Message: New Product Update!
// David - Receiving Message: New Product Update!
// Steven - Receiving Message: New Product Update!
Conclusion
In this article, we discussed the Behavioral Design Pattern known as the Observer. This pattern essentially creates a notification system that only notifies the objects that are registered to receive the updates. Learning a pattern like this is really interesting because notification systems are on just about every device you see these days, so it is relatable to every day life. It’s always a great idea to learn a new coding topic, and I really enjoyed learning about the Observer. Keep learning, and happy coding!