Description
The observer design pattern aims to set up a subscription system between one or more objects. This subscription allows sending notifications between the connected objects, such as changes made to the subscribed object.
We will therefore have several objects:
✉️ A subject that can be observed by multiple objects and will send notifications to the objects that observe it. It will transmit the emitted information to the different observers. 🔭 Observers who will observe a subject and wait for interactions, they are defined by an interface called Observer.
Situation
💬 We are creating a chat application. We could easily create a loop that displays the messages,... As you may have guessed, it would be a disaster and we don't want to hear about it! This is where the observer pattern comes to our rescue:
We have a subject that will be a topic in a chat to which we will subscribe. We have observers who will be users, who will receive messages and can send them. Thus we have a much simpler and cleaner pattern!
Advantages and disadvantages
Advantages | Disadvantages |
---|---|
✔️Low coupling, as we can add new observers without changing the subject. Indeed, the subject only knows a list of objects aligned with the observer interface and not the concrete classes of this observer. | ❌Random order: the messages sent to recipients are random as the list of observers can change. |
✔️Control: the subject does not manage its observers and this is positive as each observer can manage ignoring a message,... | ❌Unnecessary calculations in the case where we have many observers. If these observers do not manage the messages to ignore, there will be unnecessary calculations that can lead to huge consumption. |
When to use it?
💡 You have components that depend on the status of another object. 💡 It is encountered a lot in applications with graphical interfaces where when data is modified, all other data on the screen is modified.
UML diagram
Dans le cas de cet exemple, nous développerons un système de chat. Dans ce système, nous implémenterons uniquement la fonctionnalité qui permettra de recevoir les messages. Comme vous le savez, lorsque vous vous connectez à un chat vous avez la possibilité de vous inscrire à plusieurs topics. Dans ce cas, nous pouvons découper le système :
- ✉️ Le sujet sera notre topic où les utilisateurs s'inscrivent.
- 🔭 Les observateurs seront les utilisateurs qui se connectent au chat.
Dans ce cas, nous aurons une flexibilité dans notre système, qui permettra à un objet de se lier à un autre afin d'obtenir des informations.
Implémentation
Commençons par créer les bases de notre design package : l'interface Suject et l'interface Observer.
Subject
package com.design.observer.base;
public interface Subject {
public void register(Observer observer);
public void unregister(Observer observer);
public void notifyObservers();
public Object getUpdate(Observer observer);
}
🔎 Register: allows observers to register. 🔎 Unregister: allows observers to stop observing the topic. 🔎 NotifyObserver: notifies observers of a change. 🔎 getUpdate: retrieves the notified change.
Observer
package com.design.observer.base;
public interface Observer{
public void update();
public void setSubject(Observer observer);
}
🔎 Update: allows observers to retrieve the subject's update through the subject's function. 🔎 SetSubject: allows observers to register the topic.
Now that our interfaces are created, we need to add the logic that will define the methods. In the case of our ChatTopic, we must implement the Subject interface methods. To really understand the role of this topic, we will detail the interactions:
👤 Register adds a participant to the list of observers (only if it does not already exist in this list). ❌ UnRegister allows a user to withdraw from the list of observers. 📩 sendNotify informs observers of a modification, it should use the update method of the observers to retrieve the information. 🔄 getUpdate is a getter for the transmitted message. 📫 postMessage is a setter for the message.
ChatTopic
package com.design.observer.model;
import com.design.observer.base.Observer;
import com.design.observer.base.Subject;
import java.util.ArrayList;
import java.util.List;
public class ChatTopic implements Subject {
private List<Observer> observers;
private String message;
public ChatTopic() {
this.observers = new ArrayList<>();
}
@Override
public void register(Observer observer) {
if(observer == null) throw new NullPointerException("Null object/Observer");
if(!this.observers.contains(observer)) {
observers.add(observer);
}
}
@Override
public void unregister(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for(Observer observer: observers) {
observer.update();
}
}
@Override
public Object getUpdate(Observer observer) {
return this.message;
}
public void postMessage(String message) {
System.out.println("Message posted to my topic : " + message);
this.message = message;
notifyObservers();
}
}
Now that ChatTopic is defined and meets the criteria explained above! Now, we need to implement our observer class. In this class:
🔄 Update: retrieves the subject's update and displays a default message in case of a null message. ✉️ SetSubject: assigns the topic.
ChatTopicSubscriber
package com.design.observer.model;
import com.design.observer.base.Observer;
import com.design.observer.base.Subject;
public class ChatTopicSubscriber implements Observer {
private String name;
//Reference to our subject class
private Subject topic;
public ChatTopicSubscriber(String name) {
this.name = name;
}
@Override
public void update() {
String msg = (String)topic.getUpdate(this);
if(msg == null)
System.out.println(name + "No new message on this topic");
else
System.out.println(name + "Retrieving message: " + msg);
}
@Override
public void setSubject(Subject subject) {
this.topic = subject;
}
}
Now that our observers and topic are implemented, we will test this directly in the main method:
Main
package com.design.observer;
import com.design.observer.base.Observer;
import com.design.observer.model.ChatTopic;
import com.design.observer.model.ChatTopicSubscriber;
publict class Main {
public static void main(String[] args) {
ChatTopic topic = new ChatTopic();
//create observers
Observer firstObserver = new ChatTopicSubscriber("FirstObserver");
Observer secondObserver = new ChatTopicSubscriber("SecondObserver");
Observer thirdObserver = new ChatTopicSubscriber("ThirdObserver");
//Register them ...
topic.register(firstObserver);
topic.register(secondObserver);
topic.register(thirdObserver);
// Attaching observer to subject
firstObserver.setSubject(topic);
secondObserver.setSubject(topic);
thirdObserver.setSubject(topic);
//Check for updates
firstObserver.update();
thirdObserver.update();
topic.postMessage("Hello Everybody");
topic.unregister(firstObserver);
topic.postMessage("Hello Everybody");
}
}
Result:
FirstObserver: No new message on this topic
ThirdObserver: No new message on this topic
Message posted to my topic: Hello Everybody
FirstObserver Retrieving message: Hello Everybody
SecondObserver Retrieving message: Hello Everybody
ThirdObserver Retrieving message: Hello Everybody
Message posted to my topic: Hello Everybody
SecondObserver Retrieving message: Hello Everybody
ThirdObserver Retrieving message: Hello Everybody
We have now finished our implementation and we can notice that:
- We have our different observers who register on a given topic to receive messages.
- In case we want other topics, we can simply create a new class.