Observer pattern
The Observer pattern is a behavioral software design pattern that allows an object, known as the subject or publisher, to maintain a list of dependent objects, called observers or subscribers, and automatically notify them of any state changes, typically through a defined interface or callback mechanism.[1] This establishes a one-to-many dependency, promoting loose coupling between the subject and its observers by decoupling the notification logic from the subject's core functionality.[2]
First formalized in the seminal 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides—commonly referred to as the "Gang of Four" (GoF)—the Observer pattern addresses recurring challenges in object-oriented design by enabling dynamic relationships between objects without hard-coded dependencies.[3] The pattern's core structure involves a subject interface that supports attaching, detaching, and notifying observers, while observers implement an update method to react to notifications, often receiving the subject's current state or event details.[1]
The Observer pattern is particularly applicable in scenarios where an object's state change must propagate to multiple dependents, such as user interface components responding to data model updates, event-driven systems like GUI frameworks, or distributed architectures requiring push-based notifications.[2] It adheres to the Open-Closed Principle by allowing new observers to be added without modifying the subject, though it may introduce challenges like unpredictable notification order or unexpected updates if not managed carefully.[1] Widely implemented in languages and frameworks—such as .NET's IObservable<T> and IObserver<T> interfaces or Java's now-deprecated built-in support via java.util.Observable and java.util.Observer[4]—this pattern remains a foundational tool for building scalable, maintainable software systems.[2]
Introduction
Definition and Intent
The Observer pattern is a behavioral design pattern in software engineering that establishes a one-to-many dependency between objects, allowing multiple observer objects to track changes in a single subject object. In this structure, the subject maintains a dynamic list of observers and automatically notifies them of state modifications, often via a one-way broadcast communication to ensure observers remain unaware of each other's existence.[5]
The primary intent of the Observer pattern is to enable loose coupling by defining dependencies such that when the subject's state changes, all registered observers are informed and can update themselves independently, without the subject requiring knowledge of observer implementations. This approach supports the open-closed principle, facilitating extensibility as new observers can be added or removed at runtime without altering the subject's code.[5]
At its core, the pattern enforces separation of concerns: the subject focuses solely on managing its state and observer registrations, while observers handle their own synchronization logic upon notification. This decoupling enhances modularity and reusability in object-oriented systems.
A basic pseudocode outline for the subject-observer interaction illustrates the key methods:
Subject {
observers: list of Observer
state: value
attach(observer: Observer) {
add observer to observers
}
detach(observer: Observer) {
remove observer from observers
}
notify() {
for each observer in observers {
observer.update()
}
}
setState(newState: value) {
this.state = newState
notify()
}
}
Observer {
update() {
// Synchronize state with subject
}
}
Subject {
observers: list of Observer
state: value
attach(observer: Observer) {
add observer to observers
}
detach(observer: Observer) {
remove observer from observers
}
notify() {
for each observer in observers {
observer.update()
}
}
setState(newState: value) {
this.state = newState
notify()
}
}
Observer {
update() {
// Synchronize state with subject
}
}
This structure, derived from the foundational description, highlights the attachment, detachment, and notification mechanisms central to the pattern.
Historical Context and Origins
The Observer pattern emerged as a fundamental concept in object-oriented design during the 1990s, drawing heavily from event-driven programming paradigms in graphical user interfaces (GUIs). Its roots trace back to the Model-View-Controller (MVC) architecture developed in the late 1970s within the Smalltalk programming environment at Xerox PARC. Trygve Reenskaug introduced MVC in 1979 to address challenges in user interface design, where the "View" components needed to automatically update in response to changes in the underlying "Model" data, establishing a one-to-many dependency that prefigured the Observer's core mechanism.[6] This Smalltalk influence emphasized loose coupling between data subjects and their dependent observers, enabling dynamic and responsive systems in early GUI frameworks.[7]
The pattern was formally defined and popularized in 1994 through the seminal book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, collectively known as the "Gang of Four" (GoF). In this work, the Observer was classified as a behavioral design pattern, providing a reusable solution for maintaining dependencies between objects such that changes in one automatically propagate to others via notifications. The GoF drew from existing practices in Smalltalk and other object-oriented languages, codifying the pattern's structure—including subjects, observers, and update mechanisms—to promote reusability and modularity in software design.
Key milestones in the pattern's adoption occurred in the mid-1990s with its integration into Java's ecosystem. The java.util.Observable class and Observer interface were introduced in JDK 1.0 in 1996 as general-purpose tools for implementing the pattern, though AWT's core event handling at that time relied on an inheritance-based model. This evolved in JDK 1.1 with the delegation event model using listener interfaces, which more directly embodied Observer principles for GUI components.[8] This was extended in Swing, Java's advanced GUI framework from the late 1990s, which refined the approach using listener interfaces for more flexible event handling. The pattern further influenced the JavaBeans specification (version 1.01) in 1997, where the delegation event model explicitly adopted Observer principles to enable component-based development and property change propagation in reusable software beans.[9] Notably, java.util.Observable and Observer were deprecated in Java 9 (2017) due to limitations in their event model, with recommendations to use more robust alternatives like PropertyChangeSupport or reactive libraries.[10]
Over time, the Observer pattern evolved beyond its GUI origins, adapting to modern reactive programming paradigms in the 2000s and 2010s, such as Reactive Extensions (Rx) libraries, which generalize one-to-many notifications for asynchronous data streams across languages like Java and JavaScript.[1]
Applicability and Motivation
Use Cases
The Observer pattern finds primary application in scenarios where changes to the state of one object necessitate updates to multiple dependent objects, and the identities or number of those dependents are unknown or dynamic in advance. This includes situations where an abstraction comprises two interdependent aspects, such as a core data model and its representations, allowing each to vary independently through encapsulation in separate objects.[11] A key motivation for its use is to achieve loose coupling, enabling a subject to notify observers without embedding direct dependencies or assumptions about the observers' identities or behaviors.[11]
In user interface event handling, the pattern is widely employed to manage interactions like button clicks notifying registered listeners. For instance, in .NET frameworks such as Windows Forms or WPF, UI components act as subjects that raise events upon user actions, allowing observers (event handlers) to respond without tight integration between the component and the response logic, thus promoting modular design.[2] Similarly, in data binding within MVC architectures, the model serves as the subject, notifying multiple views (observers) of state changes to ensure synchronized displays, as seen in systems where user interfaces must reflect real-time data updates without hardcoded view dependencies.
Industry examples illustrate its practicality in monitoring and synchronization contexts. Weather monitoring systems often implement the pattern where a central weather data subject notifies attached display observers (e.g., current conditions, forecasts) of measurement changes, ensuring all views update automatically without the subject knowing specific display implementations.[11] In distributed systems, it supports state synchronization, such as stock price updates where a ticker service as subject broadcasts price alterations to multiple observer clients (e.g., trading dashboards, alerts), maintaining consistency across components without direct wiring.[2] Logging systems can also leverage it, with an event subject notifying various output observers (e.g., file, console, database) of log entries, allowing flexible extension of logging destinations.[11]
The pattern is best applied when loose coupling is essential, such as in extensible systems where subjects must broadcast notifications to varying sets of observers over time, assuming familiarity with object-oriented principles like encapsulation to manage observer registration and state privacy.[11]
Benefits and Trade-offs
The Observer pattern promotes loose coupling between the subject and its observers, as the subject maintains only a reference to the observer interface without depending on concrete observer classes, allowing independent variation of subjects and observers.[1] This decoupling adheres to the open-closed principle, enabling the addition of new observers without modifying the subject's code, which enhances extensibility in systems where dependencies evolve over time. Additionally, the pattern facilitates broadcast communication, where a single state change in the subject triggers notifications to multiple observers efficiently, supporting scalability in one-to-many relationships without explicit enumeration of recipients.
Despite these advantages, the pattern introduces trade-offs, including the potential for unexpected cascading updates, as observers may trigger further notifications in other subjects, leading to complex chains of reactions that are difficult to anticipate or control.[1] Debugging becomes challenging due to indirect dependencies, where tracing the flow of control requires examining observer registrations and notification paths rather than direct method invocations. Furthermore, maintaining the list of observers incurs overhead in terms of memory for storage and computational cost for iterations during notifications, particularly when the observer count grows large.[12]
The Observer pattern should be avoided in high-performance real-time systems where notification latency must be minimized, as the indirection and potential for multiple callbacks can introduce unpredictable delays compared to direct synchronous calls.[13] It is also unsuitable when the number of observers is fixed and small, since the administrative overhead of managing registrations outweighs the benefits, making simpler direct method calls more efficient.[1]
In terms of code organization, the pattern reduces duplication relative to ad-hoc direct method calls, where each subject would need hardcoded references to specific observers; instead, a single notification mechanism centralizes the logic, avoiding repetitive update code across multiple subjects or observer additions.
Structure and Participants
Key Components
The Observer pattern defines a one-to-many dependency between objects, where a central entity known as the Subject maintains a list of dependent objects called Observers and notifies them of any state changes, ensuring automatic updates across the system.
The Subject, often implemented as a concrete class called ConcreteSubject, is responsible for managing its internal state and the collection of attached observers. It provides operations such as attach() to add an observer to the list, detach() to remove one, and notify() to broadcast changes to all registered observers when its state is modified. The ConcreteSubject stores the relevant state data that observers depend on and initiates notifications only upon significant changes, decoupling the broadcasting mechanism from the specific reactions of observers.
The Observer is typically defined as an abstract class or interface that specifies a single update() method, which observers must implement to receive and handle notifications from the subject. This method is invoked by the subject during the notification process, allowing observers to respond to state changes without needing to poll the subject continuously. By standardizing this interface, the pattern enables polymorphism, where any concrete implementation can be attached to the subject without altering its code.
A ConcreteObserver implements the Observer interface and maintains a reference to the ConcreteSubject to synchronize its own state with the subject's. Upon receiving an update() notification, the ConcreteObserver typically queries the subject for the current state details—often by calling a getter method—and performs the necessary actions, such as updating its local data or triggering dependent behaviors. This pull-based approach allows observers to decide how much or what specific information to retrieve, promoting flexibility in handling notifications.
In terms of interactions, the Subject broadcasts notifications to all attached ConcreteObservers whenever its state changes, iterating through its observer list and invoking each update() method in sequence. Observers, in turn, can query the Subject for additional context if needed, but the Subject itself remains unaware of the observers' internal logic or identities beyond the interface level. This separation of responsibilities ensures the Subject focuses solely on state management and notification logistics, while observers encapsulate their reaction-specific code, fostering loose coupling and reusability.
UML Class and Sequence Diagrams
The UML class diagram for the Observer pattern illustrates the static structure of the participants and their relationships, emphasizing a one-to-many dependency between the subject and multiple observers.[14] At the core is the Subject class, which maintains an internal list of observers (typically represented as a collection like a list or set) and provides operations such as attach(Observer), detach(Observer), notify(), and optionally getState() to manage state access.[15] The Observer is defined as an abstract class or interface featuring an update() method that observers must implement to receive notifications.[14] Concrete implementations include ConcreteSubject, which extends Subject and holds the actual state data along with methods to modify it (e.g., setState()), and one or more ConcreteObserver classes that extend Observer, each with their own update() logic to react to changes.[15] Associations are depicted with a unidirectional dependency arrow from Subject to Observer, showing the one-to-many multiplicity (1 on the Subject side to 0..* on the Observer side), highlighting how a single subject can notify multiple independent observers without tight coupling.[14]
The UML sequence diagram captures the dynamic behavior of the Observer pattern, demonstrating the runtime interactions during observer attachment, state change, and notification.[16] It typically begins with a Client object sending attach() messages to the Subject to register one or more ConcreteObserver instances, establishing the dependency.[17] When the Subject's state changes (e.g., via an internal changeState() or setState() call), it triggers its notify() method, which iterates over the observer list and sends update() messages to each ConcreteObserver in sequence.[18] Each observer then responds by performing its update logic, such as querying the subject's state or reacting to the notification.[19] Lifelines represent the participants (Client, Subject, Observers), with activation bars indicating active periods, and arrows showing synchronous message flows, underscoring the broadcast nature of notifications.
Key UML notations in these diagrams reinforce the pattern's design principles. Multiplicity is shown as 1..* for the observers attached to a subject, indicating the potential for zero or more observers per subject.[14] Dependency arrows (dashed lines with open arrows) denote the loose coupling, where subjects depend on the Observer interface rather than concrete classes.[15] Notes or stereotypes may clarify variations, such as the optional push model (where notify() passes state data directly to update(state) ) versus the pull model (where update() without parameters prompts observers to call getState() on the subject), allowing flexibility in data transfer without altering the core structure.[11]
These diagrams collectively highlight the Observer pattern's emphasis on loose coupling and runtime binding, as the subject's notifications are dynamically dispatched to registered observers without compile-time knowledge of their specific types or implementations, enabling extensible and maintainable designs.[20] The class diagram provides a blueprint for the roles and relationships, while the sequence diagram visualizes the broadcast mechanism, illustrating how changes propagate efficiently across decoupled components as defined in the participant responsibilities.[11]
Implementation
Registering and Managing Observers
In the Observer pattern, the subject maintains a collection of observers, typically implemented as a list or set, to track dependencies. The attach operation adds an observer to this collection, enabling it to receive future notifications, while the detach operation removes an observer to cease notifications and prevent unnecessary processing. These operations, as defined in the original pattern specification, allow for dynamic management of observer registrations without altering the subject's core logic.[21]
To ensure reliability, the detach operation should include null checks and verify the observer's presence in the collection before removal, avoiding exceptions or redundant operations. Similarly, the attach operation benefits from checking for existing registrations, using a set-based collection to enforce uniqueness and prevent duplicate entries that could lead to multiple unintended notifications. This approach promotes efficient resource use and maintains the integrity of the observer list.
Effective management strategies include employing weak references for observers within the subject's collection, which allows garbage collection of unreferenced observers without explicit detachment, thereby mitigating memory leaks in long-lived subjects. In concurrent environments, thread-safety is achieved by synchronizing access to the observer collection during attach and detach operations, often through locks or concurrent data structures like thread-safe lists, to prevent race conditions during registration changes.[22][23]
Best practices emphasize making attach and detach operations idempotent: repeated attachments of the same observer should result in no additional entries, and detachments of unregistered observers should have no effect, simplifying client code and reducing errors. Additionally, implementations should focus on active observers only during management, ensuring detached or invalid entries are promptly cleaned to optimize storage and performance.[23]
Notification Mechanism
In the Observer pattern, the notification mechanism is triggered by the subject's notify operation, which is invoked whenever the subject's state changes significantly. This operation iterates through the list of registered observers and calls the update() method on each one in sequence, ensuring all dependents are informed of the change.[1] The process is typically synchronous, blocking the subject's thread until all notifications complete, which maintains simplicity but can introduce delays if observers perform time-intensive tasks.[24] Asynchronous alternatives, such as queuing notifications in a separate thread or event dispatcher, allow the subject to proceed without waiting, though they add complexity in managing delivery order and potential race conditions.[23]
State changes are propagated using one of two transfer models: the push model or the pull model. In the push model, the subject actively sends the relevant data or event details as arguments to the observers' update() methods, minimizing the need for observers to query the subject and thus reducing coupling.[1] Conversely, the pull model involves the subject passing only a reference to itself (or a signal) in the update() call, prompting observers to retrieve specific state information by querying the subject directly; this approach offers flexibility for observers to access only the data they require but increases their dependency on the subject's interface.[25]
The update() method supports customization through optional parameters, such as event hints or contextual data, enabling observers to react appropriately to nuanced changes without needing multiple method variants.[1] Notifications can also be filtered to target specific observers, for instance by checking observer attributes or interests before invoking update(), which optimizes delivery in systems with heterogeneous dependents.[2]
For robustness, the notify operation incorporates error handling to achieve graceful degradation: if an observer's update() invocation raises an exception, the subject catches it, logs the issue if appropriate, and continues notifying the remaining observers to prevent a single failure from disrupting the entire propagation.[23] This design ensures reliability in multi-observer scenarios, aligning with the pattern's goal of loose coupling without introducing cascading errors.[26]
Publish-Subscribe Differences
The Observer pattern establishes a direct relationship between the subject (publisher) and its observers (subscribers), where the subject maintains an explicit list of observers and notifies them synchronously upon state changes, leading to coupling in space and time but loose dependency implementation. In this setup, observers must register directly with the subject, creating a scenario where both parties have knowledge of each other, though the subject's interface remains independent of specific observer implementations. This direct coupling suits scenarios where tight coordination within a single process is acceptable and efficient.
The publish-subscribe pattern, by contrast, employs an intermediary event broker or service that fully decouples publishers from subscribers in terms of space, time, and notification flow, as publishers send events to the broker without knowing who or how many subscribers exist. Subscribers register their interests with the broker, which handles routing and delivery, often asynchronously, eliminating the need for direct references and enabling anonymous interactions. This architecture supports distributed environments where components may evolve independently, contrasting with Observer's more localized, in-process focus.[27]
Observer is best chosen for straightforward, non-distributed applications with a limited number of observers, such as ensuring data consistency in local caches or user interface updates within a single application. Publish-subscribe excels in complex, scalable systems like event-driven architectures or networked services, where subjects must remain unaware of observers to avoid maintenance burdens and support dynamic scaling. The Observer pattern can be seen as a simplified variant of publish-subscribe, lacking the broker and thus inheriting some of the latter's decoupling benefits in a constrained form.
A key divergence lies in subscription flexibility: publish-subscribe allows selective notifications through topic-based, content-based, or type-based filtering at the broker level, enabling subscribers to receive only relevant events without processing irrelevant ones. In Observer, notifications are typically broadcast to all registered observers indiscriminately, requiring observers to filter events themselves if needed.[24]
Observer Variations
The Observer pattern supports two primary notification styles: push and pull. In the push model, the subject sends detailed state information or event data directly to observers during notification, allowing immediate reactions without additional queries. This approach is efficient when the data is small and uniform but can increase coupling if details change. Conversely, the pull model has the subject notify observers of a change, after which observers query the subject for the necessary information. This reduces the data sent in notifications and maintains encapsulation but may lead to multiple queries if many observers request the same data. The choice depends on the application's needs for performance, security, and loose coupling.[1]
Integration with Other Patterns
The Observer pattern integrates seamlessly with the Model-View-Controller (MVC) architectural pattern, primarily by facilitating loose coupling between the Model and View components. In MVC, the Model acts as the subject, notifying registered Views (as observers) of state changes, allowing multiple views to update independently without direct dependencies on the model. This decoupling enhances maintainability, as views can be added or modified without altering the underlying data logic. Additionally, the Controller can serve as a subject in certain implementations, propagating user inputs to the model while leveraging Observer notifications to refresh views accordingly. Effective integration requires a foundational understanding of MVC components, where the Model encapsulates data and business logic, Views handle presentation, and Controllers manage interactions.[28]
Beyond MVC, the Observer pattern combines with the Strategy pattern to enable pluggable update behaviors among observers. By encapsulating different notification handling algorithms as interchangeable strategies, observers can dynamically select update logic—such as simple logging versus complex data processing—without modifying the subject's core notification mechanism. This synergy promotes flexibility in systems requiring varied responses to the same event, as seen in MVC where Strategy defines controller behaviors that interact with Observer-driven updates.[1]
The Observer pattern also integrates with the Composite pattern to support hierarchical observer structures, particularly in tree-like data representations. In such setups, composite observers treat individual leaves and nested groups uniformly, allowing notifications to propagate through the hierarchy—for instance, updating a parent node that cascades changes to child observers in a graphical user interface tree. This combination is evident in MVC applications, where Composite structures views into nested components that subscribe to model changes via Observer.[29]
In advanced applications, the Observer pattern forms the foundation of Reactive Extensions (Rx) libraries, where it underlies observable streams for handling asynchronous and event-based data flows. Rx extends the classic Observer by introducing observables that emit sequences of values over time, enabling composable operations like filtering and transforming notifications in reactive programming paradigms. This integration powers scalable systems in domains such as user interfaces and real-time data processing, building on the pattern's one-to-many notification core.[2][30]
Limitations and Mitigations
Reference Management Issues
In implementations of the Observer pattern, strong references from the subject to its list of observers can prevent the garbage collector from reclaiming memory for observers that are no longer needed elsewhere in the application, leading to memory leaks, especially when observers outlive the subject or form reference cycles.[31][32] This issue arises because the subject's observer list maintains direct, strong pointers to observer objects, keeping them alive indefinitely unless explicitly removed.
To mitigate these leaks, developers can employ weak references for storing observers, such as Java's WeakReference class, which allows the garbage collector to reclaim observer objects when no other strong references exist, without preventing notifications to active observers.[33] Additionally, implementing explicit cleanup mechanisms, like a dispose or unregister method that removes observers from the subject's list, ensures timely release of references in scenarios where weak references alone are insufficient.[31]
Memory leaks from improper reference management in the Observer pattern are particularly prevalent in long-running applications, such as servers, where objects persist over extended periods and subtle cycles can accumulate unreclaimed memory. Detection typically involves profiling tools like heap analyzers or garbage collection logs to identify retained objects and reference chains; for instance, Java profilers such as VisualVM can generate heap dumps to trace leak suspects back to observer lists.[34][35]
A key trade-off with weak references is the potential for premature garbage collection of observers if they lack external strong references, resulting in missed notifications when the subject attempts to update a now-null reference.[33] This requires careful design, such as periodic cleanup of nullified weak references in the observer list, to balance memory efficiency against reliability.[31]
In large-scale applications, the Observer pattern faces significant scalability challenges, particularly when dealing with a high number of observers or frequent state changes in the subject. A key issue is the occurrence of notification storms, where rapid or continuous updates to the subject result in an overwhelming volume of notifications to observers, leading to resource exhaustion and degraded system responsiveness. This problem is exacerbated in real-time systems, such as user interfaces or event-driven architectures, where unchecked propagation can cause cascading delays and increased CPU utilization.[36]
Another critical performance concern stems from the pattern's inherent linear scaling behavior. The subject's notification process typically involves iterating over a list of all registered observers. As observer counts grow—common in distributed systems or modular applications with many dependents—this can introduce substantial latency, especially under concurrent loads, potentially bottlenecking the entire system and limiting horizontal scalability.[37][38]
To address these issues, developers can implement mitigation strategies focused on optimizing notification delivery. Throttling restricts the frequency of notifications to a defined rate, while debouncing consolidates multiple rapid updates into a single event, such as by waiting for a quiet period before propagating changes; these techniques are particularly effective in scenarios like UI event handling to maintain reactivity without overload. Batching updates further enhances efficiency by aggregating several state changes into one notification payload, reducing the total number of observer calls and minimizing overhead in high-throughput environments.[36]
For improved temporal decoupling, asynchronous queuing mechanisms allow the subject to enqueue notifications rather than delivering them synchronously, preventing blocking when observers are slow or unavailable. Event queues buffer updates, enabling the subject to continue operations independently while observers process events at their own pace, thus supporting fault tolerance and better resource utilization in decoupled systems.[39]
Evaluating these aspects requires rigorous testing practices. Load testing with simulated observer counts greater than 1000 helps uncover scalability limits by mimicking peak usage, revealing how the system behaves under stress. Complementing this, profiling notification latency—using tools to track execution times and identify hotspots in the update loop—ensures optimizations are targeted and measurable, prioritizing overall system performance over isolated components.[40]
Examples
Java Implementation
The Observer pattern in Java can be implemented using the legacy java.util.Observable class and java.util.Observer interface, which were part of the standard library since Java 1.0 to provide built-in support for the pattern.[41] However, these classes have been deprecated since Java 9 because they offer a limited event model that does not support rich event types and introduce threading inconsistencies, such as lack of thread-safety in observer registration and notification.[42] Developers are encouraged to implement the pattern manually for better control, flexibility, and adherence to modern Java practices.[43]
A simple legacy example uses a weather station as the subject (WeatherData extending Observable) that notifies displays implementing Observer when temperature changes. The subject maintains an internal state (e.g., temperature) and calls setChanged() followed by notifyObservers() to push updates.
java
import java.util.Observable;
import java.util.Observer;
class WeatherData extends Observable {
private float temperature;
public float getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
setChanged();
notifyObservers(temperature); // Push model: passes data to observers
}
}
class CurrentConditionsDisplay implements Observer {
private float temperature;
@Override
public void update(Observable o, Object arg) {
if (arg instanceof Float) {
this.temperature = (Float) arg;
display();
}
}
public void display() {
System.out.println("Current temperature: " + temperature + " degrees Celsius");
}
}
// Usage
public class LegacyObserverDemo {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherData.addObserver(display);
weatherData.setTemperature(25.0f); // Output: Current temperature: 25.0 degrees Celsius
}
}
import java.util.Observable;
import java.util.Observer;
class WeatherData extends Observable {
private float temperature;
public float getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
setChanged();
notifyObservers(temperature); // Push model: passes data to observers
}
}
class CurrentConditionsDisplay implements Observer {
private float temperature;
@Override
public void update(Observable o, Object arg) {
if (arg instanceof Float) {
this.temperature = (Float) arg;
display();
}
}
public void display() {
System.out.println("Current temperature: " + temperature + " degrees Celsius");
}
}
// Usage
public class LegacyObserverDemo {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherData.addObserver(display);
weatherData.setTemperature(25.0f); // Output: Current temperature: 25.0 degrees Celsius
}
}
This approach demonstrates the push model, where the subject directly supplies updated data to observers via the notifyObservers(Object) method.[41]
For a modern implementation, define custom interfaces: Observer<T> for receiving updates and Subject<T> for managing observers, using a List<Observer<T>> to store them. This avoids inheritance from Observable (which violates composition principles) and leverages generics for type-safe notifications, ensuring compile-time checks on the data type passed in updates.[42] The weather station scenario again serves as a demo, with the subject notifying observers on temperature changes using a push model.
java
import java.util.ArrayList;
import java.util.List;
interface Observer<T> {
void update(T data);
}
interface Subject<T> {
void attach(Observer<T> observer);
void detach(Observer<T> observer);
void notifyObservers();
}
class WeatherStation implements Subject<Float> {
private List<Observer<Float>> observers = new ArrayList<>();
private float temperature;
@Override
public void attach(Observer<Float> observer) {
observers.add(observer);
}
@Override
public void detach(Observer<Float> observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer<Float> observer : observers) {
observer.update(temperature); // Push model: passes data directly
}
}
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers();
}
public float getTemperature() {
return temperature;
}
}
class CurrentConditionsDisplay implements Observer<Float> {
private float temperature;
@Override
public void update(Float temperature) {
this.temperature = temperature;
display();
}
private void display() {
System.out.println("Current temperature: " + temperature + " degrees Celsius");
}
}
// Usage
public class ModernObserverDemo {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherStation.attach(display);
weatherStation.setTemperature(25.0f); // Output: Current temperature: 25.0 degrees Celsius
weatherStation.detach(display);
}
}
import java.util.ArrayList;
import java.util.List;
interface Observer<T> {
void update(T data);
}
interface Subject<T> {
void attach(Observer<T> observer);
void detach(Observer<T> observer);
void notifyObservers();
}
class WeatherStation implements Subject<Float> {
private List<Observer<Float>> observers = new ArrayList<>();
private float temperature;
@Override
public void attach(Observer<Float> observer) {
observers.add(observer);
}
@Override
public void detach(Observer<Float> observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer<Float> observer : observers) {
observer.update(temperature); // Push model: passes data directly
}
}
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers();
}
public float getTemperature() {
return temperature;
}
}
class CurrentConditionsDisplay implements Observer<Float> {
private float temperature;
@Override
public void update(Float temperature) {
this.temperature = temperature;
display();
}
private void display() {
System.out.println("Current temperature: " + temperature + " degrees Celsius");
}
}
// Usage
public class ModernObserverDemo {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherStation.attach(display);
weatherStation.setTemperature(25.0f); // Output: Current temperature: 25.0 degrees Celsius
weatherStation.detach(display);
}
}
This custom implementation aligns with the Observer pattern's structure as defined in the foundational work on design patterns, promoting loose coupling between the subject and its observers through interfaces. The use of generics enhances type safety, preventing runtime errors from mismatched update data types, which was a weakness in the legacy API.[42]
Python Implementation
In Python, the Observer pattern is implemented using custom classes that leverage the language's dynamic typing and abstract base classes (ABCs) from the abc module to define interfaces without strict enforcement. The core components include a Subject class that maintains a collection of observers and provides methods for registration, unregistration, and notification, and an Observer ABC that requires an update method to handle state changes. This approach allows for loose coupling, where subjects notify observers of events like state updates without direct dependencies.[44]
A representative example is an inventory management system, where a ConcreteSubject represents the inventory stock, and observers such as a supplier and customer react to stock level changes. The ConcreteSubject tracks the stock state via a private attribute and notifies attached observers when the stock updates, such as after sales or restocking. Concrete observers implement the update method to perform actions like ordering more items if stock is low (supplier) or alerting about availability (customer). To prevent memory leaks from circular references—common in long-lived subjects holding strong references to observers—the subject's observer list uses weakref.WeakSet from the weakref module, ensuring observers can be garbage-collected when no longer needed elsewhere.[44]
Here is a complete code implementation:
python
from abc import ABC, abstractmethod
from typing import List
from weakref import WeakSet
class Observer(ABC):
"""
Abstract observer.
"""
@abstractmethod
def update(self, subject_state: int):
"""
Receive update from subject.
:param subject_state: New state of subject.
"""
pass
class Subject(ABC):
"""
Abstract subject.
"""
@abstractmethod
def attach(self, observer: Observer):
pass
@abstractmethod
def detach(self, observer: Observer):
pass
@abstractmethod
def notify(self):
pass
class Inventory(Subject):
"""
Concrete subject: Inventory stock.
"""
def __init__(self):
self._stock = 0 # Stock level
self._observers: WeakSet = WeakSet() # Weak references to prevent leaks
@property
def stock(self):
return self._stock
@stock.setter
def stock(self, value: int):
self._stock = value
self.notify()
def attach(self, observer: Observer):
self._observers.add(observer)
def detach(self, observer: Observer):
# WeakSet removes dead references automatically
if observer in self._observers:
self._observers.discard(observer)
def notify(self):
"""
Trigger an update in each observer.
"""
for observer in list(self._observers): # Copy to avoid modification issues
if observer is not None: # Check for dead weak refs
observer.[update](/page/Update)(self.stock)
class SupplierObserver(Observer):
"""
Concrete observer: Supplier reorders if low stock.
"""
def update(self, stock: int):
if stock < 10:
print(f"Supplier: Stock low ({stock}), ordering more.")
class CustomerObserver(Observer):
"""
Concrete observer: Customer notified of stock changes.
"""
def update(self, stock: int):
print(f"Customer: Stock updated to {stock}.")
# Usage
if __name__ == "__main__":
inventory = Inventory()
supplier = SupplierObserver()
customer = CustomerObserver()
inventory.attach(supplier)
inventory.attach(customer)
inventory.stock = 15 # Triggers notifications
inventory.stock = 5 # Triggers notifications
inventory.detach(customer)
inventory.stock = 20 # Only supplier notified
from abc import ABC, abstractmethod
from typing import List
from weakref import WeakSet
class Observer(ABC):
"""
Abstract observer.
"""
@abstractmethod
def update(self, subject_state: int):
"""
Receive update from subject.
:param subject_state: New state of subject.
"""
pass
class Subject(ABC):
"""
Abstract subject.
"""
@abstractmethod
def attach(self, observer: Observer):
pass
@abstractmethod
def detach(self, observer: Observer):
pass
@abstractmethod
def notify(self):
pass
class Inventory(Subject):
"""
Concrete subject: Inventory stock.
"""
def __init__(self):
self._stock = 0 # Stock level
self._observers: WeakSet = WeakSet() # Weak references to prevent leaks
@property
def stock(self):
return self._stock
@stock.setter
def stock(self, value: int):
self._stock = value
self.notify()
def attach(self, observer: Observer):
self._observers.add(observer)
def detach(self, observer: Observer):
# WeakSet removes dead references automatically
if observer in self._observers:
self._observers.discard(observer)
def notify(self):
"""
Trigger an update in each observer.
"""
for observer in list(self._observers): # Copy to avoid modification issues
if observer is not None: # Check for dead weak refs
observer.[update](/page/Update)(self.stock)
class SupplierObserver(Observer):
"""
Concrete observer: Supplier reorders if low stock.
"""
def update(self, stock: int):
if stock < 10:
print(f"Supplier: Stock low ({stock}), ordering more.")
class CustomerObserver(Observer):
"""
Concrete observer: Customer notified of stock changes.
"""
def update(self, stock: int):
print(f"Customer: Stock updated to {stock}.")
# Usage
if __name__ == "__main__":
inventory = Inventory()
supplier = SupplierObserver()
customer = CustomerObserver()
inventory.attach(supplier)
inventory.attach(customer)
inventory.stock = 15 # Triggers notifications
inventory.stock = 5 # Triggers notifications
inventory.detach(customer)
inventory.stock = 20 # Only supplier notified
This code demonstrates notification: when the stock property is set, it triggers notify(), which iterates over the weak set and calls update on live observers. The use of @property and @setter decorators provides controlled access to the subject's state, encapsulating changes and automatically invoking notifications.[44]
Python's features enhance the pattern further; for instance, context managers from the contextlib module can temporarily attach observers during specific operations, such as a transaction block, ensuring detachment upon exit to avoid unintended notifications. A simple context manager might wrap attach and detach calls:
python
from contextlib import contextmanager
class Inventory(Subject):
# ... (previous code)
@contextmanager
def temporary_observer(self, observer: Observer):
self.attach(observer)
try:
yield
finally:
self.detach(observer)
from contextlib import contextmanager
class Inventory(Subject):
# ... (previous code)
@contextmanager
def temporary_observer(self, observer: Observer):
self.attach(observer)
try:
yield
finally:
self.detach(observer)
This allows scoped observation, like with inventory.temporary_observer(temp_customer): inventory.stock = 10, limiting the observer's lifetime.
A functional variation replaces class-based observers with callbacks, storing callable functions in the subject's list instead of observer instances. The notify method then invokes each callback with the state, promoting simplicity for one-off handlers without full class overhead. For example, replace the observer list with a list of callables and call callback(self.stock) in notify. This aligns with Python's emphasis on functions as first-class objects.[44]
JavaScript Implementation
In JavaScript, the Observer pattern leverages the language's prototypal inheritance and event-driven nature to establish a one-to-many dependency between a subject (the observable) and multiple observers. The subject maintains an array of observer functions or objects, providing methods to add, remove, and notify them of state changes, often using plain objects or ES6 classes for encapsulation. This setup aligns with JavaScript's dynamic typing and allows for flexible, runtime-defined subscriptions without requiring a formal interface.[45]
A typical implementation uses an ES6 class for the subject, storing observers in an array and iterating over them during notifications to update dependent components, such as UI elements in a browser environment. For instance, consider a data source like a text input that notifies observers to update a word count display whenever its value changes, mimicking DOM event handling. The following code demonstrates this:
javascript
class EventObserver {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
unsubscribe(fn) {
this.observers = this.observers.filter(subscriber => subscriber !== fn);
}
broadcast(data) {
this.observers.forEach(subscriber => subscriber(data));
}
}
const blogObserver = new EventObserver();
function getWordCount(text) {
return text.trim().split(/\s+/).length;
}
blogObserver.subscribe((text) => {
const blogCount = document.getElementById('blogWordCount');
blogCount.textContent = getWordCount(text);
});
const blogPost = document.getElementById('blogPost');
blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value));
class EventObserver {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
unsubscribe(fn) {
this.observers = this.observers.filter(subscriber => subscriber !== fn);
}
broadcast(data) {
this.observers.forEach(subscriber => subscriber(data));
}
}
const blogObserver = new EventObserver();
function getWordCount(text) {
return text.trim().split(/\s+/).length;
}
blogObserver.subscribe((text) => {
const blogCount = document.getElementById('blogWordCount');
blogCount.textContent = getWordCount(text);
});
const blogPost = document.getElementById('blogPost');
blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value));
In this example, the EventObserver acts as the subject, integrating seamlessly with the browser's DOM via addEventListener to trigger broadcasts on user input, ensuring real-time UI updates without tight coupling between the data source and display logic.[45]
JavaScript's modern features enhance this pattern for asynchronous scenarios. Promises can wrap notifications to handle async observer callbacks, allowing the subject to resolve or reject based on operation outcomes and chain further updates, such as fetching external data before notifying observers. For example, a subject's notifyObservers method might return a Promise that awaits all observer promises, enabling coordinated async responses in browser applications.[46]
Additionally, the built-in EventTarget interface provides a native Observer variant, where objects like DOM elements serve as subjects with methods addEventListener for subscription, removeEventListener for unsubscription, and dispatchEvent for notification, directly supporting event bubbling and delegation in browser contexts. This API underpins much of JavaScript's event system, allowing custom event objects to propagate changes across the DOM tree efficiently.[47]