Introducing the Observer Design Pattern

Tram Ho

Source: refactoring.guru

Observer

Other names: Event-Subscriber, Listener

Scheme

Observer is a behavioral design pattern (hereinafter referred to as DP) that defines a subscription mechanism that notifies multiple objects of events occurring to the object they are observing. (observe).

Problem

Imagine that you have two types of audiences: Khách hàng and Cửa hàng . The customer is very interested in a particular brand of product (say a new iPhone model) going on sale in the store.

Customers can visit the store every day to check if the product is available. But while the product is yet to be released, most trips to this store would be meaningless.

On the other hand, the store may send tons of emails (which customers think is spam) to all customers every time a new product is available. This will save some customers from going to the store countless times, however, at the same time, it will annoy other customers who are not interested in the new product.

Looks like there’s a conflict going on here. Either customers waste time checking to see if a product is in stock, or the store wastes resources by notifying customers who don’t want to receive notifications.

Solution

The object that has some state that other object is interested in is usually called subject , but since it will also notify other objects of changes to its state, we’ll call it publisher. . All other audiences that want to track changes to the publisher ‘s status are called subscribers .

Observer DP suggests that we should add subscriptions to the publisher class so that individual objects can subscribe or unsubscribe from the event stream coming from that publisher. It sounds complicated, but the reality is simpler than you think😃In practice, this mechanism involves 1) an array for storing the list of references to subscriber objects, and 2) several public methods that allow adding and deleting subscribers.

Now, whenever a critical event happens to the publisher, the publisher runs through the subscriber array and calls the specific notification method of those objects.

Real applications can have dozens of different subscriber classes interested in keeping track of the events of the same publisher class. In this case, we shouldn’t couple publisher into all of those classes. Besides, we may not even know about some of those classes in advance if our publisher class is written for the purpose of being used by others.

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

If your application has many different types of publishers and you want to make the subscriber compatible with all publishers, you can make all publishers follow the same interface. This interface only needs to describe some of the subscribe methods. The interface will allow the subscriber to observe the publisher’s state without couple to specific publisher classes.

For example with reality

If you subscribe to a newspaper or magazine, you don’t have to go to the store to check if the next issue is available. Instead, the publisher sends new issues directly to your inbox immediately after publication or even before.

Publishers keep a list of subscribers and know which journals they are interested in. Subscribers can leave the list at any time when they no longer want the publisher to send them new issue issues.

Structure

  1. The publisher fires events that are of interest to other objects. These events occur when the publisher changes state or performs some action. Publisher has a subscribe mechanism that allows objects to go in / out of a subscriber list.
  2. When a new event occurs, the publisher traverses the subscriber list and calls the notification method declared in the subscriber interface of each subscriber object.
  3. The Subscriber interface declares the notification interface. In most cases, it includes only one method, update . The method can have several parameters that allow the publisher to pass some event details along with the update event.
  4. The specific Subscriber class takes some action in response to messages issued by the publisher. All of these classes must implement the same interface so that the publisher is not couple with specific classes.
  5. Usually, the subscriber needs some contextual information to properly handle the update event. For this reason, the publisher usually passes some context data as the param of the message method. The publisher can pass itself as a parameter, allowing the subscriber to directly fetch any data it needs.
  6. The client creates separate publisher and subscriber objects and then subscribes to the publisher to listen for publisher updates.

Fake code

Dynamically compiled subscriber lists: Objects can start or stop listening for notifications at runtime, depending on the desired behavior of your application.

In this implementation, the class editor does not automatically save the subscribe list. It delegates this work to a special helper object specifically for that. You can upgrade that object to turn it into a centralized event dispatcher, allowing any object to act as a publisher.

Adding new subscribers to the program does not require changes to existing publisher classes, as long as they work with all subscribers through the same interface.

Applicability

*** Use the Observer pattern when changes to an object’s state may require the alteration of other objects and the actual object list is not known or dynamically changed. * * You can often experience this problem when working with GUI classes. Example: You have created custom button classes and you want to allow the client to connect some custom code to those buttons so that the code is activated whenever the user clicks on a button. Observer DP allows any object to implement a subscriber subscribe interface to receive event notifications from the publisher object. You can add subscriptions to your nodes, allowing clients to connect to their custom code through custom subscriber classes.

*** Use this DP when some objects in your app have to observe others, but only for a limited time or under specific circumstances. ** The subscribe list is dynamic, so the subscriber can enter or exit the list whenever it needs to.

How to implement

  1. Look at your business logic and try to divide it into two parts: core functionality, independent of other code -> this will be the publisher; the rest will turn into a set of subscriber classes.
  2. Declare the subscriber interface. At a minimum, it must declare an update method.
  3. Declare the publisher interface and describe two methods: adding a subscriber object and removing that object from the list. Remember that publishers can only work with subscribers through the subscriber interface.
  4. Decide where to place the actual subscribe list and implementations of the subscribe methods. Usually this code will be the same for all publisher types, so the obvious place to put it is in an inherit abstraction class directly from the publisher interface. The publisher specific class extends that class, inheriting the subscribe behavior. However, if you are applying this DP to the existing class system, consider the composition-based approach: placing the subscribe logic on a separate object and getting all real publishers to use it.
  5. Create publisher specific classes. Every time something important happens within the publisher, the publisher has to notify all of his subscribers.
  6. Implement methods of update notification in specific subscriber classes. Most subscribers will need some contextual data about the event. That data can be passed as a param of the notification method. But there is another option, which is that when the subscriber receives the message, the subscriber can pull any data directly from the message. In this case, the publisher must pass itself through the update method. The less flexible option is to link the publisher with the perpetual subscriber through an initiation method.
  7. The client must create all necessary subscribers and subscribe to them with the appropriate publishers.

Advantages and disadvantages

Pros 1: Open / Closed Principle: You can add new subscriber classes without having to change the code of the publisher class (and vice versa if there is a publisher interface). Pros 2: You can establish relationships between objects at runtime. Cons: Not in control of the order in which the subscriber receives the message.

Relationship with other DPs

  • The Chain of Responsibility, Command, Mediator, and Observer are different solutions to the problem of connecting the sender and receiver of the request: The Chain of Responsibility passes a sequence request along a dynamic chain of potential recipients for until one of them processes the request. Command establishes a one-way connection between sender and receiver. Mediator eliminates direct connections between senders and receivers, forcing them to communicate indirectly through an intermediary. The Observer allows the recipient to dynamically subscribe and unsubscribe from the request.
  • The difference between Mediator and Observer is usually not great in many cases. In most cases, you can implement one of these DPs; but sometimes you can do both. Let’s see how we do that.

The Mediator’s main goal is to eliminate interdependencies among a set of system components. Instead, these components become dependent on a single intermediate object. The Observer’s goal is to establish one-way dynamic connections between objects, where some objects act as subordinates of others.

There is a popular Mediator implementation that is based on the Observer. The mediator object acts as the publisher, and the components act as subscribers subscribe and unsubscribe from the mediator events. When Mediator is implemented in this way, it may look very similar to Observer.

When you find it confusing, remember that you can implement Mediator in other ways. For example, you can permanently associate all components with the same Mediator object. This implementation will not be the same as the Observer but will still be a Mediator version.

Now imagine a program where all the components have become publishers, allowing dynamic connections between each other. There will be no centralized mediator object, only one distributed observer group.

Share the news now

Source : Viblo