Skip to content

Observer Design Pattern (Behavioral)

Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.


Imagine you have two types of objects: a Customer and a Store. The customer is very interested in a particular brand of device (e.g., a new iPhone) which should become available in the store very soon.

The customer could visit the store every day to check product availability. But while the product is still en route, most of these trips would be pointless. On the other hand, the store could send thousands of emails (which might be considered spam) to all customers each time a new product becomes available. This would save some customers from endless trips to the store, but it would annoy others who have no interest in the new products.

The object that has some interesting state is often called the subject, but since it’s also going to notify other objects about changes to its state, we’ll call it the Publisher. All other objects that want to track changes to the publisher’s state are called Subscribers.

The Observer pattern suggests that you add a subscription mechanism to the publisher class so individual objects can subscribe to or unsubscribe from a stream of events coming from that publisher. This mechanism consists of:

  1. An array field for storing a list of references to subscriber objects.
  2. Several public methods which allow adding subscribers to and removing them from that list.

Now, whenever an important event happens to the publisher, it goes over its subscribers and calls the specific notification method on their objects.

If you subscribe to a newspaper or magazine, you no longer need to go to the store to check if the next issue is available. Instead, the publisher sends new issues directly to your mailbox right after publication or even in advance.

The publisher maintains a list of subscribers and knows which magazines they’re interested in. You can stop being a subscriber at any time when you want the publisher to stop sending new magazine issues to you.


  1. Publisher issues events of interest to other objects. These events occur when the publisher changes its state or executes some behaviors. Publishers contain a subscription infrastructure that lets new subscribers join and current subscribers leave the list.
  2. Subscriber Interface declares the notification interface. In most cases, it consists of a single update method.
  3. Concrete Subscribers perform some actions in response to notifications issued by the publisher. All of these classes must implement the same interface so the publisher isn’t coupled to concrete classes.
  4. Client creates publisher and subscriber objects separately and then registers subscribers for publisher updates.
classDiagram
    class Publisher {
        -subscribers: Subscriber[]
        +subscribe(s: Subscriber)
        +unsubscribe(s: Subscriber)
        +notifySubscribers()
        +mainBusinessLogic()
    }
    class Subscriber {
        <<interface>>
        +update(context)
    }
    class ConcreteSubscriberA {
        +update(context)
    }
    class ConcreteSubscriberB {
        +update(context)
    }

    Publisher o-- Subscriber
    Subscriber <|-- ConcreteSubscriberA
    Subscriber <|-- ConcreteSubscriberB
    Client ..> Publisher
    Client ..> ConcreteSubscriberA

# The publisher interface includes subscription management
# methods and a notification method.
class EventManager:
private field listeners: hash map of lists of listeners
method subscribe(eventType, listener):
listeners.add(eventType, listener)
method unsubscribe(eventType, listener):
listeners.remove(eventType, listener)
method notify(eventType, data):
foreach (listener in listeners.of(eventType)):
listener.update(data)
# The concrete publisher contains real business logic that's
# interesting for some subscribers.
class Editor:
public field events: EventManager
private field file: File
constructor Editor():
events = new EventManager()
method openFile(path):
this.file = new File(path)
events.notify("open", file.name)
# The subscriber interface declares the update method.
interface EventListener:
method update(filename)

  • One-to-Many Dependencies: Use the Observer pattern when changes to the state of one object may require changing other objects, and the actual set of objects is unknown beforehand or changes dynamically.
  • Temporary Monitoring: Use the pattern when some objects in your app must observe others, but only for a limited time or in specific cases.
  1. Look at your business logic and try to break it down into two parts: the core functionality, independent of other code, will act as the publisher; the rest will turn into a set of subscriber classes.
  2. Declare the subscriber interface. At a minimum, it should declare a single update method.
  3. Declare the publisher interface and describe the methods for adding a subscriber object to and removing it from the list.
  4. Decide where to put the actual subscription list and the implementation of subscription methods (usually a base class is best).
  5. Create concrete subscriber classes. Implement the update method.
  6. The client must create all necessary subscribers and register them with the right publishers.

Pros:

  • Open/Closed Principle: You can introduce new subscriber classes without having to change the publisher’s code (and vice-versa if there’s a publisher interface).
  • You can establish relations between objects at runtime.

Cons:

  • Subscribers are notified in random order.
  • Chain of Responsibility, Command, Mediator, and Observer address various ways of connecting senders and receivers of requests.
  • The difference between Mediator and Observer is that Mediator centralizes communication between a set of objects, while Observer distributes it by letting objects subscribe to events.

// Observer
public interface IObserver
{
void Update(float temperature);
}
// Subject
public class WeatherStation
{
private readonly List&lt;IObserver&gt; _observers = new();
private float _temperature;
public void AddObserver(IObserver observer) => _observers.Add(observer);
public void RemoveObserver(IObserver observer) => _observers.Remove(observer);
public void SetTemperature(float temp)
{
_temperature = temp;
NotifyObservers();
}
private void NotifyObservers()
{
foreach (var obs in _observers)
obs.Update(_temperature);
}
}
// Concrete Observer
public class Display : IObserver
{
public void Update(float temperature)
{
Console.WriteLine($"Temperatura atual: {temperature}Β°C");
}
}
// Usage
class Program
{
static void Main()
{
var station = new WeatherStation();
var display = new Display();
station.AddObserver(display);
station.SetTemperature(22.5f);
station.SetTemperature(25.0f);
}
}