Observer. This article describes the Observer design pattern and its usage in the programming language Java.
1. The Observer Pattern
1.1. Definition
The Observer Pattern defines a one-to-many dependency between objects, so that when one object (the subject) changes its state, all registered observers are notified and updated automatically.
Observers (or listeners) watch for changes in the subject and react accordingly, making this pattern particularly useful in scenarios where a change in one part of the system needs to be communicated to multiple other parts.
1.2. Example
The Observer Pattern is widely used in Java, particularly in graphical user interfaces (GUIs). For instance, consider a button listener in a UI. When the button is pressed, the listener is notified and executes a specific action.
This pattern is not limited to UI components. For example, imagine an application that monitors the current temperature. One component (Part A) displays the temperature, while another component (Part B) turns on a green light if the temperature exceeds 20 degrees Celsius.
Part B registers itself as an observer of Part A. When the temperature in Part A changes, it triggers an event that notifies all registered listeners, including Part B, which can then update its display accordingly.
The following code illustrates a listener implementation for a button in a UI:
Button button = new Button(shell, SWT.PUSH);
// Register listener for the selection event
button.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
System.out.println("Button was pressed!");
}
});
1.3. Code Example
In this example, the observer watches for changes in a List
of Person
objects. To demonstrate this, create a new Java project called com.vogella.java.designpattern.observer and implement the following classes:
package com.vogella.java.designpattern.observer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
public class MyModel {
public static final String FIRSTNAME = "firstName";
public static final String LASTNAME = "lastName";
private List<Person> persons = new ArrayList<>();
private List<PropertyChangeListener> listeners = new ArrayList<>();
public class Person {
private String firstName;
private String lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
notifyListeners(this, FIRSTNAME, this.firstName, this.firstName = firstName);
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
notifyListeners(this, LASTNAME, this.lastName, this.lastName = lastName);
}
}
public List<Person> getPersons() {
return persons;
}
public MyModel() {
// Hard-code some test data
persons.add(new Person("Lars", "Vogel"));
persons.add(new Person("Jim", "Knopf"));
}
private void notifyListeners(Object object, String property, String oldValue, String newValue) {
for (PropertyChangeListener listener : listeners) {
listener.propertyChange(new PropertyChangeEvent(this, property, oldValue, newValue));
}
}
public void addChangeListener(PropertyChangeListener newListener) {
listeners.add(newListener);
}
}
package com.vogella.java.designpattern.observer;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class MyObserver implements PropertyChangeListener {
public MyObserver(MyModel model) {
model.addChangeListener(this);
}
@Override
public void propertyChange(PropertyChangeEvent event) {
System.out.println("Changed property: " + event.getPropertyName() + " [old -> "
+ event.getOldValue() + "] | [new -> " + event.getNewValue() + "]");
}
}
package com.vogella.java.designpattern.observer;
import com.vogella.java.designpattern.observer.MyModel.Person;
public class Main {
public static void main(String[] args) {
MyModel model = new MyModel();
MyObserver observer = new MyObserver(model);
// Change the last name of each person; observer will be notified
for (Person person : model.getPersons()) {
person.setLastName(person.getLastName() + " Smith");
}
// Change the first name of each person; observer will be notified
for (Person person : model.getPersons()) {
person.setFirstName(person.getFirstName() + " Jr.");
}
}
}
1.4. Evaluation
The Observer Pattern adheres to the Open/Closed Principle, which states that classes should be open for extension but closed for modification.
One of the major advantages of the Observer Pattern is its flexibility in adding new observers at runtime. The subject doesn’t need to know the concrete type of its observers. It only requires that the observers conform to a common interface. This allows for a more flexible design, where new observer types can be introduced without modifying the subject code.
Furthermore, the pattern is inherently event-driven, and many modern frameworks rely on this pattern to handle event management. It allows for the creation of loosely coupled systems, which are easier to maintain, extend, and test. As applications become more complex, this ability to manage updates between different parts of the system becomes critical for maintaining a clean, modular architecture.
Moreover, the Observer Pattern decouples the subject from its observers, meaning that only the observers have direct knowledge of the subject. This promotes a more modular design, where components can evolve independently.
Additionally, observers can be dynamically added or removed, making it adaptable to changing requirements.
1.5. Variations of the Observer Pattern
The Observer Pattern can be implemented in various ways, each tailored to specific use cases. Here are a few variations:
-
Push vs. Pull Model:
-
Push Model: The subject sends detailed information about its state to observers whenever a change occurs. This is useful when observers need specific data to react to changes.
-
Pull Model: Observers request the state of the subject after being notified of a change. This model is advantageous when observers may need to handle the state in a specific manner or when the subject’s state is large or complex.
-
-
Event Aggregator:
-
This variation centralizes event handling. Instead of having observers listen directly to subjects, they subscribe to a central event aggregator that manages the communication. This is useful in decoupling components in larger systems.
-
-
Reactive Programming:
-
In modern applications, especially those involving real-time data processing, reactive programming frameworks (like RxJava) extend the Observer Pattern. They provide more powerful abstractions for handling asynchronous data streams and event-driven programming.
-
1.6. Use Cases
The Observer Pattern is applicable in various scenarios:
-
User Interface Components: As demonstrated in the button listener example, UI frameworks frequently use this pattern to respond to user actions.
-
Data Binding: In applications using Model-View-ViewModel (MVVM) architecture, changes in the model automatically update the view without direct references between them.
-
Event Management Systems: Systems that require notification of various events (e.g., logging systems, event-driven architectures) often utilize this pattern to keep components in sync.
-
Stock Price Monitoring: Applications that track financial data can use the Observer Pattern to notify clients about price changes in real-time.
1.7. Best Practices
To effectively use the Observer Pattern, consider the following best practices:
-
Decouple Observers and Subjects: Ensure that the subject has minimal knowledge about its observers. This promotes flexibility, as you can add or remove observers without impacting the subject.
-
Limit Observer Responsibilities: Observers should focus on their specific tasks and not perform actions that should belong to the subject. This keeps the architecture clean and maintainable.
-
Use Weak References: In long-lived applications, consider using weak references for observers to avoid memory leaks. This allows the garbage collector to reclaim observer memory when necessary.
-
Avoid Circular Dependencies: Ensure that observers do not create a situation where they depend on the state of the subject, which in turn depends on them. This can lead to unpredictable behavior and complicate debugging.
-
Document Event Lifecycle: Clearly document the events that subjects will emit and the expected behaviors of observers. This is particularly important in larger teams or when the pattern is reused across multiple components.
1.8. Summary
The Observer Pattern is a powerful design pattern that facilitates communication between objects while promoting loose coupling. By allowing subjects to notify multiple observers of state changes, this pattern enables flexibility and scalability in software design.
When implemented thoughtfully, the Observer Pattern can enhance the maintainability and adaptability of your applications. As systems grow and evolve, leveraging this pattern can ensure that components remain synchronized while minimizing interdependencies.
2. Links and Literature
2.1. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting