In the last article, I have analysed a behavioural design pattern that reduces dependencies between a set of interacting objects by decoupling the interaction logic from the objects and moving it to a dedicated controller — Mediator. In this article, I would like to analyse and implement another behavioural design pattern that lets you define a publish-subscribe mechanism to notify multiple objects about any events that happen to the object they’re subscribed to — it is Observer.
- What is the Observer design pattern?
- Your Contribution
Observer, also known as Dependents or Publish-Subscribe, belongs to the category of behavioural design patterns. The intention of this design pattern is described in the GoF book:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Spoiler alert: if you have ever heard about reactive programming or even used the related frameworks/libraries/tools such as ReactiveX, RxDart or just basic streams in Dart, this design pattern won’t be a game-changer to you. But it is still worth knowing how reactive programming ideas are implemented in the OOP context from the ground up, though.
The motivation for this design pattern comes from the problem of having a collection of tightly coupled objects in the system where changes for one object should trigger changes in the others (one-to-many relationship). An inflexible way to implement this is to define an object that implements updating the state of other dependent ones. Such object becomes hard to implement, maintain, test and reuse because of the dependency chaos.
A better way to approach this is to implement a publish-subscribe mechanism that sends the update events to dependent objects so they could implement and maintain the update logic on their own. To achieve this, the Observer design pattern introduces two roles: Subject and Observer.
The subject is the publisher of notifications that also defines a way for the observers to subscribe/unsubscribe from those notifications. A subject may have any number of dependent observers — the same idea of maintaining a one-to-many relationship just in a more flexible way. When a subject changes state, all registered observers are notified and updated automatically. This way the subject could trigger an update on dependent objects without even knowing who its observers are — this enables loose coupling between subject and observers.
Let’s move to the analysis and implementation parts to understand and learn the details about this pattern and how to implement it!
The general structure of the Observer design pattern looks like this:
- Publisher (Subject) — provides an interface for attaching and detaching Subscriber (Observer) objects, contains a list of observers;
- (Optional) Concrete Publishers — stores state of interest for Concrete Subscribers and sends a notification to its observers when the state changes. This class is optional when only a single type of Publisher is needed. In such case, the state and notification logic is handled by the Publisher;
- Subscriber (Observer) — declares the notification interface for objects that should be notified of changes in a Subject;
- Concrete Subscribers — implements the Subscriber (Observer) interface to keep its state consistent with the subject’s state;
- Client — creates Subject and Observer objects, attaches observers to subject updates.
If you have read the previous article in the series or are familiar with the Mediator design pattern you could feel some kind of déjà vu — isn’t the Observer design pattern the same thing? Let me explain.
The primary goal of the Mediator design pattern is to replace many-to-many relationships between objects with one-to-many relationship by using the dedicated mediator object that handles the communication part. Observer allows establishing a dynamic one-way connection between objects, where some objects act as subordinates of others.
If you have only one mediator that allows subscriptions to its state, this implementation is based on the Observer design pattern but the Mediator design pattern could be also used only as a part of the publish-subscribe communication. Now, if we have multiple publishers and multiple subscribers (which could be publishers as well), there won’t be any mediator object only a distributed set of observers.
The Observer design pattern should be used when a change to one object requires changing others, but you don’t know how many objects need to be changed and how. The pattern allows subscribing to such object events and changing the dependent object’s state accordingly.
Also, this pattern should be used when some objects must observe others, but only for a limited time. The subscription mechanism allows dependent objects to listen to the update events on demand and change this behaviour at run-time.
For the implementation part, we will use the Observer design pattern to implement a stock market prototype.
In the stock market, there are hundreds and thousands of different stocks. Of course, not all of them are relevant to you, so you would like to subscribe and track only the specific ones.
The prototype allows subscribing to 3 different stocks — GameStop (GME), Alphabet Inc. a.k.a. Google (GOOGL) and Tesla Motors (TSLA). Also, there are two different types of subscriptions:
- Default stock subscription — notifies about every subscribed stock change.
- Growing stock subscription — notifies only about the growing stock changes.
Such stocks’ tracker could be easily implemented by using the Observer design pattern. Of course, the prototype only supports 3 different stock types, but new stock tickers or even new subscription types could be easily added later on without affecting the existing code. Let’s check the class diagram first and then implement the pattern!
The class diagram below shows the implementation of the Observer design pattern:
StockTicker is an abstract class that is used as a base class for all the specific stock ticker classes. The class contains title, stockTimer and stock properties, subscribers list, provides several methods:
- subscribe() — subscribes to the stock ticker;
- unsubscribe() — unsubscribes from the stock ticker;
- notifySubscribers() — notifies subscribers about the stock change;
- setStock() — sets stock value;
- stopTicker() — stops ticker emitting stock events.
GameStopStockTicker, GoogleStockTicker and TeslaStockTicker are concrete stock ticker classes that extend the abstract class StockTicker.
Stock class contains symbol, changeDirection, price and changeAmount properties to store info about the stock.
StockTickerSymbol is an enumerator class defining supported stock ticker symbols — GME, GOOGL and TSLA.
StockChangeDirection is an enumerator class defining stock change directions — growing and falling.
StockSubscriber is an abstract class that is used as a base class for all the specific stock subscriber classes. The class contains title, id and stockStreamController properties, stockStream getter and defines the abstract update() method to update subscriber state.
DefaultStockSubscriber and GrowingStockSubscriber are concrete stock subscriber classes that extend the abstract class StockSubscriber.
An abstract class implementing base methods for all the specific stock ticker classes. Property title is used in the UI for stock ticker selection, stockTimer periodically emits a new stock value that is stored in the stock property by using the setStock() method. The class also stores a list of stock subscribers that can subscribe to the stock ticker and unsubscribe from it by using the subscribe() and unsubscribe() respectively. Stock ticker subscribers are notified about the value change by calling the notifySubscribers() method. The stock timer could be stopped by calling the stopTicker() method.
All of the specific stock ticker classes extend the abstract StockTicker class.
- GameStopStockTicker — a stock ticker of the GameStop stocks that emits a new stock event every 2 seconds.
- TeslaStockTicker — a stock ticker of the Tesla stocks that emits a new stock event every 3 seconds.
- GoogleStockTicker — a stock ticker of the Google stocks that emits a new stock event every 5 seconds.
A simple class to store information about the stock. Stock class contains data about the stocker ticker symbol, stock change direction, current price and the change amount.
A special kind of class — enumeration — to define supported stock ticker symbols. Also, there is a StockTickerSymbolExtension defined where the toShortString() method returns a short version of the enumeration string value.
A special kind of class — enumeration — to define stock change directions.
An abstract class containing base properties for all the specific stock ticker classes. Property title is used in the UI for stock subscriber selection, id uniquely identifies the subscriber. Updated stock values are added to the stockStreamController and emitted via the stockStream. Abstract method update() is defined and must be implemented by all the concrete stock subscriber classes.
- DefaultStockSubscriber — a default stock subscriber that emits every stock change on update.
- GrowingStockSubscriber — a growing stock subscriber that emits only growing stock changes on update.
First of all, a markdown file is prepared and provided as a pattern’s description:
ObserverExample contains a list of StockSubscriber as well as a list of StockTickerModel objects (specific StockTicker class with a flag of whether the user is subscribed to the stock ticker or not).
A specific subscriber class could be easily changed by using the StockSubscriberSelection widget. Also, StockTickerSelection allows easily subscribe/unsubscribe from the specific stock ticker at run-time.
As you can see in the example, the subscription type could be easily changed at run-time, you could start and stop tracking of the specific stocks at any point as well.
All of the code changes for the Observer design pattern and its example implementation could be found here.
💖 or 🦄 this article to show your support and motivate me to write better!
💬 Leave a response to this article by providing your insights, comments or wishes for the next topic.
📢 Share this article with your friends, colleagues on social media.
➕ Follow me on dev.to or any other social media platform.
⭐ Star the Github repository.