Recently, I have been studying design patterns related to the Observer pattern. I will be learning about the Observer pattern from the following aspects:
- What is the Observer design pattern?
- Understanding key concepts.
- Ways to notify observers.
- Implementation of the Observer pattern.
- Advantages and disadvantages of the Observer pattern.
- Use cases.
What is the Observer design pattern?#
The Observer pattern is a software design pattern that defines a one-to-many relationship between objects. When an object's data changes, it notifies other objects that depend on it, allowing them to respond to the data changes. This design pattern, which establishes a one-to-many notification relationship when the data of the target object changes, is called the Observer design pattern.
Understanding key concepts#
The Observer design pattern mainly distinguishes between two concepts:
- Observer: Refers to the observer object, which is the subscriber of the message.
- Subject: Refers to the target object to be observed, which is the publisher of the message.
Ways to notify observers#
When the subject's data changes, there are two main ways to notify the observers:
- Push: The message is broadcasted to the observers, and the observers can only passively and unconditionally receive it.
- Pull: The observer receives a notification from the subject and can independently decide to retrieve the message.
Implementation of the Observer pattern#
Below are two ways to implement the Observer design pattern:
- Implementing the Observer pattern manually:
First, create the subject that observers will observe:
/**
* The target object that observers want to observe.
*/
public abstract class Subject {
protected ArrayList<Observer> observerList = new ArrayList<>();
public void registerObserver(Observer obs) {
observerList.add(obs);
}
public void unRegisterObserver(Observer obs) {
observerList.remove(obs);
}
public void notifyAllObserver(){
for (Observer observer : observerList) {
observer.update(this);
}
}
}
Create the concrete subject:
/**
* The concrete target object.
*/
public class ConcreteSubject extends Subject{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObserver();
}
}
Next, define the observer interface for convenience:
/**
* Observer interface.
*/
public interface Observer {
void update(Subject subject);
}
Create the concrete observer:
/**
* The concrete observer.
*/
public class ConcreteObserver implements Observer{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
@Override
public void update(Subject subject) {
ConcreteSubject concreteSubject = (ConcreteSubject)subject;
state = concreteSubject.getState();
}
}
Finally, test the Observer pattern:
/**
* Main class.
*/
public class Client {
public static void main(String[] args) {
ConcreteSubject concreteSubject = new ConcreteSubject();
ConcreteObserver obs1 = new ConcreteObserver();
ConcreteObserver obs2 = new ConcreteObserver();
ConcreteObserver obs3 = new ConcreteObserver();
concreteSubject.observerList.add(obs1);
concreteSubject.observerList.add(obs2);
concreteSubject.observerList.add(obs3);
concreteSubject.setState(10);
System.out.println("Observer obs1: " + obs1.getState());
System.out.println("Observer obs2: " + obs2.getState());
System.out.println("Observer obs3: " + obs3.getState());
}
}
The expected output is:
Observer obs1: 10
Observer obs2: 10
Observer obs3: 10
By changing the data of the target object, the data of the corresponding observers is updated, achieving message subscription and sending.
- Using the Observer and Observable provided by the Java API:
Create a class that extends Observable as the subject:
/**
* The subject (target object).
*/
public class ConcreteSubject extends Observable{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
setChanged();
notifyObservers(state);
}
}
Create a class that implements Observer as the observer:
/**
* The observer.
*/
public class ConcreteObserver implements Observer{
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
@Override
public void update(Observable arg0, Object arg1) {
ConcreteSubject concreteSubject = (ConcreteSubject) arg0;
this.state = concreteSubject.getState();
}
}
Finally, test the Observer pattern:
/**
* Test the Observer pattern.
*/
public class Client {
public static void main(String[] args) {
ConcreteSubject concreteSubject = new ConcreteSubject();
ConcreteObserver obs1 = new ConcreteObserver();
ConcreteObserver obs2 = new ConcreteObserver();
ConcreteObserver obs3 = new ConcreteObserver();
concreteSubject.addObserver(obs1);
concreteSubject.addObserver(obs2);
concreteSubject.addObserver(obs3);
concreteSubject.setState(100);
System.out.println("Observer obs1: " + obs1.getState());
System.out.println("Observer obs2: " + obs2.getState());
System.out.println("Observer obs3: " + obs3.getState());
}
}
The expected output is:
Observer obs1: 100
Observer obs2: 100
Observer obs3: 100
Advantages and disadvantages of the Observer pattern#
- Advantages: The observer and subject are abstractly coupled, allowing for a stable message triggering mechanism.
- Disadvantages: If the subject has multiple indirect observers, the message delivery will consume more time. If there is a circular dependency between the observer and subject, it may eventually lead to system crashes.
Use cases#
The Observer design pattern is widely used in development, mainly in the following scenarios:
- In processes such as games and chats, where messages are forwarded from the server to the client.
- The broadcast mechanism in Android and the notification of data changes in ListView also use the Observer design pattern.
- Subscription-related systems, where subscribers are synchronized with updated topics.
This concludes the Observer design pattern.