Observer - Koll3g/DesignPatterns GitHub Wiki

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.

Problem

Imagine that you have two types of objects: a Customer and a Store. The customer is very interested in a particular brand of product (say, it’s a new model of the iPhone) which should become available in the store very soon.

The customer could visit the store every day and check product availability. But while the product is still en route, most of these trips would be pointless.

Visiting the store vs. sending spam

On the other hand, the store could send tons 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. At the same time, it’d upset other customers who aren’t interested in new products.

It looks like we’ve got a conflict. Either the customer wastes time checking product availability or the store wastes resources notifying the wrong customers.

Solution

The object that has some interesting state is often called subject, but since it’s also going to notify other objects about the changes to its state, we’ll call it 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. Fear not! Everything isn’t as complicated as it sounds. In reality, this mechanism consists of 1) an array field for storing a list of references to subscriber objects and 2) several public methods which allow adding subscribers to and removing them from that list.

A subscription mechanism lets individual objects subscribe to event notifications.

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

Real apps might have dozens of different subscriber classes that are interested in tracking events of the same publisher class. You wouldn’t want to couple the publisher to all of those classes. Besides, you might not even know about some of them beforehand if your publisher class is supposed to be used by other people.

That’s why it’s crucial that all subscribers implement the same interface and that the publisher communicates with them only via that interface. This interface should declare the notification method along with a set of parameters that the publisher can use to pass some contextual data along with the notification.

Publisher notifies subscribers by calling the specific notification method on their objects.

If your app has several different types of publishers and you want to make your subscribers compatible with all of them, you can go even further and make all publishers follow the same interface. This interface would only need to describe a few subscription methods. The interface would allow subscribers to observe publishers’ states without coupling to their concrete classes.

Pseudocode

Notifying objects about events that happen to other objects.

` // The base publisher class includes subscription management // code and notification methods. class EventManager is private field listeners: hash map of event types and listeners

method subscribe(eventType, listener) is
    listeners.add(eventType, listener)

method unsubscribe(eventType, listener) is
    listeners.remove(eventType, listener)

method notify(eventType, data) is
    foreach (listener in listeners.of(eventType)) do
        listener.update(data)

// The concrete publisher contains real business logic that's // interesting for some subscribers. We could derive this class // from the base publisher, but that isn't always possible in // real life because the concrete publisher might already be a // subclass. In this case, you can patch the subscription logic // in with composition, as we did here. class Editor is public field events: EventManager private field file: File

constructor Editor() is
    events = new EventManager()

// Methods of business logic can notify subscribers about
// changes.
method openFile(path) is
    this.file = new File(path)
    events.notify("open", file.name)

method saveFile() is
    file.write()
    events.notify("save", file.name)

// ...

// Here's the subscriber interface. If your programming language // supports functional types, you can replace the whole // subscriber hierarchy with a set of functions. interface EventListener is method update(filename)

// Concrete subscribers react to updates issued by the publisher // they are attached to. class LoggingListener implements EventListener is private field log: File private field message: string

constructor LoggingListener(log_filename, message) is
    this.log = new File(log_filename)
    this.message = message

method update(filename) is
    log.write(replace('%s',filename,message))

class EmailAlertsListener implements EventListener is private field email: string private field message: string

constructor EmailAlertsListener(email, message) is
    this.email = email
    this.message = message

method update(filename) is
    system.email(email, replace('%s',filename,message))

// An application can configure publishers and subscribers at // runtime. class Application is method config() is editor = new Editor()

    logger = new LoggingListener(
        "/path/to/log.txt",
        "Someone has opened the file: %s")
    editor.events.subscribe("open", logger)

    emailAlerts = new EmailAlertsListener(
        "[email protected]",
        "Someone has changed the file: %s")
    editor.events.subscribe("save", emailAlerts)

`

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.