Observer Pattern
The Observer Pattern is used when there is a one-to-many relationship between objects. For example, if one object is modified, its dependent objects are automatically notified. The Observer Pattern is a behavioral pattern.
Introduction
Intent: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Main Solution: The problem of notifying other objects about a state change of an object, considering ease of use and low coupling for high collaboration.
When to Use: When the state of one object (the subject) changes, all its dependent objects (observers) should be notified and updated.
How to Solve: Use object-oriented techniques to weaken this dependency.
Key Code: An ArrayList in the abstract class to store observers.
Examples:
- In an auction, the auctioneer observes the highest bid and notifies other bidders.
- In "Journey to the West," Sun Wukong asks the Bodhisattva to subdue the Red Boy. The Bodhisattva sprinkles water on the ground, summoning a turtle, which is an observer watching the Bodhisattva's action.
Advantages:
- Observer and subject are loosely coupled.
- Establishes a trigger mechanism.
Disadvantages:
- If an observed object has many direct and indirect observers, notifying all of them can take a lot of time.
- Circular dependencies between observers and the subject can trigger cyclic calls, potentially causing system crashes.
- Observers have no mechanism to know how the observed object changed, only that it has changed.
Usage Scenarios:
- When an abstract model has two aspects, one dependent on the other. Encapsulating these aspects in separate objects allows them to be changed and reused independently.
- When an object's change leads to changes in one or more other objects, without knowing how many objects will change, to reduce coupling.
- When an object must notify other objects without knowing who these objects are.
- When creating a trigger chain in a system, where the behavior of object A affects object B, B affects C, and so on, the Observer Pattern can create a chain-like trigger mechanism.
Notes:
- Java already has support classes for the Observer Pattern.
- Avoid circular references.
- If executed sequentially, an error in one observer can block the system; asynchronous execution is generally preferred.
Implementation
The Observer Pattern uses three classes: Subject, Observer, and Client. The Subject object has methods to bind and unbind observers to the Client object. We create the Subject class, Observer abstract class, and concrete classes extending the Observer abstract class.
ObserverPatternDemo, our demonstration class, uses Subject and concrete class objects to demonstrate the Observer Pattern.
Step 1
Create the Subject class.
Subject.java
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observers
= new ArrayList<Observer>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObservers();
}
public void attach(Observer observer){
observers.add(observer);
}
public void notifyAllObservers(){
for (Observer observer : observers) {
observer.update();
}
}
}
Step 2
Create the Observer class.
Observer.java
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
Step 3
Create concrete observer classes.
BinaryObserver.java
public class BinaryObserver extends Observer{
public BinaryObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Binary String: "
+ Integer.toBinaryString( subject.getState() ) );
}
}
OctalObserver.java
public class OctalObserver extends Observer{
public OctalObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Octal String: "
+ Integer.toOctalString( subject.getState() ) );
}
}
HexaObserver.java
public class HexaObserver extends Observer{
public HexaObserver(Subject subject){
this.subject = subject;
this.subject.attach(this);
}
@Override
public void update() {
System.out.println( "Hex String: "
+ Integer.toHexString( subject.getState() ).toUpperCase() );
}
}
Step 4
Use Subject and concrete observer objects.
ObserverPatternDemo.java
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new Subject();
new HexaObserver(subject);
new OctalObserver(subject);
new BinaryObserver(subject);
System.out.println("First state change: 15");
subject.setState(15);
System.out.println("Second state change: 10");
subject.setState(10);
}
}
Step 5
Execute the program, outputting the following results:
First state change: 15
Hex String: F
Octal String: 17
Binary String: 1111
Second state change: 10
Hex String: A
Octal String: 12
Binary String: 1010