Understanding the Observer Design Pattern in Flutter Development
Written on
What is the Observer Design Pattern?
In the previous article, I explored a behavioral design pattern known as Mediator, which minimizes dependencies among a group of interacting entities by isolating the interaction logic into a dedicated controller. This time, I will delve into another behavioral design pattern that facilitates a publish-subscribe mechanism, allowing multiple objects to be informed of any changes related to the object they are observing—this is the Observer pattern.
Update 2022–09–15: For a more engaging reading experience and to access the latest articles, interactive code examples, and additional content at no cost, please visit kazlauskas.dev.
Overview of the Observer Pattern
The Observer design pattern, also referred to as Dependents or Publish-Subscribe, falls under the category of behavioral design patterns. Its primary goal is articulated in the "Gang of Four" (GoF) book:
Define a one-to-many dependency between objects so that when one object alters its state, all its dependents are automatically notified and updated.
If you’re familiar with reactive programming or have used frameworks and tools like ReactiveX, RxDart, or basic Dart streams, the essence of this design pattern may not be revolutionary for you. Nonetheless, understanding how these reactive programming concepts are applied in the context of object-oriented programming (OOP) is still valuable.
The rationale behind this design pattern arises from the challenge of managing a collection of tightly coupled objects in a system, where a change in one object necessitates changes in others (a one-to-many relationship). A rigid way to implement this would be to create a single object responsible for updating the state of its dependents. Such an object can become complex, making implementation, maintenance, testing, and reuse difficult due to dependency chaos.
Instead, a more effective approach involves establishing a publish-subscribe mechanism that sends update notifications to dependent objects, allowing them to manage and maintain their own update logic. This pattern introduces two key roles: Subject and Observer.
The subject acts as the notification publisher and provides a mechanism for observers to subscribe to or unsubscribe from notifications. A subject can have multiple dependent observers, thereby maintaining a one-to-many relationship in a more flexible manner. When the subject’s state changes, all registered observers are notified and updated automatically. This allows the subject to trigger updates in dependent objects without needing to know who its observers are, fostering loose coupling between the subject and its observers.
Let's proceed to analyze and implement this pattern to grasp its details and application!
Analysis
The structure of the Observer design pattern is illustrated below:
- Publisher (Subject): Provides an interface for attaching and detaching Subscriber (Observer) objects and maintains a list of observers.
- (Optional) Concrete Publishers: Holds state relevant to Concrete Subscribers and sends notifications when this state changes. This class is optional if only one type of Publisher is required.
- Subscriber (Observer): Defines the notification interface for objects that need to be informed of changes in a Subject.
- Concrete Subscribers: Implements the Subscriber (Observer) interface to ensure its state aligns with the subject’s state.
- Client: Creates Subject and Observer objects and connects them.
Observer vs. Mediator
If you’ve read the previous article in this series or are acquainted with the Mediator design pattern, you may notice some similarities—could the Observer pattern be the same? Allow me to clarify.
The Mediator design pattern aims to replace many-to-many relationships among objects with one-to-many relationships through a dedicated mediator that manages communication. The Observer pattern, on the other hand, allows for establishing a dynamic one-way connection between objects, where some act as subordinates to others.
In cases where there is a single mediator allowing subscriptions to its state, this implementation follows the Observer design pattern. However, Mediator can also be utilized as part of the publish-subscribe communication. In scenarios with multiple publishers and subscribers (the latter also being potential publishers), there is no mediator; only a distributed network of observers exists.
Applicability
The Observer design pattern is applicable when a change in one object necessitates updates in others, even when the exact number of objects needing changes is unknown. This pattern facilitates subscriptions to object events, allowing dependent objects to adjust their state accordingly.
Additionally, it is useful when certain objects must observe others for a limited duration. The subscription mechanism enables dependent objects to listen for update events as needed and modify this behavior at runtime.
Implementation
For our implementation, we’ll use the Observer design pattern to create a stock market prototype.
In the stock market, there are numerous stocks available. Naturally, not all stocks are of interest, so you might want to subscribe only to specific ones. This prototype allows subscriptions to three different stocks: GameStop (GME), Alphabet Inc. (Google, GOOGL), and Tesla Motors (TSLA). There are two types of subscriptions available:
- Default Stock Subscription: Notifies about every change in the subscribed stocks.
- Growing Stock Subscription: Notifies only about changes in stocks that are increasing in value.
This approach can be efficiently implemented using the Observer design pattern. While the prototype currently supports three stock types, new stock tickers or subscription types can easily be added later without disrupting existing code. Let’s first examine the class diagram before we proceed to implementation!
Class Diagram
The class diagram below illustrates how the Observer design pattern is implemented:
- StockTicker: An abstract class serving as a base for specific stock ticker classes. This class contains properties like title, stockTimer, and stock, along with methods for subscribing, unsubscribing, notifying subscribers, setting stock values, and stopping the ticker.
- GameStopStockTicker, GoogleStockTicker, TeslaStockTicker: Concrete stock ticker classes extending the StockTicker abstract class.
- Stock: A simple class that stores information about the stock, including the ticker symbol, change direction, price, and change amount.
- StockTickerSymbol: An enumeration defining supported stock ticker symbols—GME, GOOGL, and TSLA.
- StockChangeDirection: An enumeration defining stock change directions—growing or falling.
- StockSubscriber: An abstract class that serves as a base for all specific stock subscriber classes. It includes properties for title, id, and stockStreamController, along with a stockStream getter and an abstract update() method for updating subscriber state.
- DefaultStockSubscriber and GrowingStockSubscriber: Concrete classes extending StockSubscriber.
Concrete Stock Ticker Classes
Each specific stock ticker class extends the abstract StockTicker class.
- GameStopStockTicker: Emits a new stock event every 2 seconds.
- TeslaStockTicker: Emits a new stock event every 3 seconds.
- GoogleStockTicker: Emits a new stock event every 5 seconds.
Stock Class
The Stock class is a simple structure that holds information about the stock, including its ticker symbol, change direction, current price, and change amount.
StockTickerSymbol and StockChangeDirection
These are enumeration classes that define the supported stock ticker symbols and stock change directions, respectively.
StockSubscriber
The StockSubscriber class serves as a base class for specific stock subscriber implementations. It includes properties for UI representation, a unique identifier, and a mechanism for updating stock values through a stream.
Concrete Stock Subscriber Classes
- DefaultStockSubscriber: A subscriber that emits notifications for every stock change.
- GrowingStockSubscriber: A subscriber that notifies only when there are increases in stock value.
Example Implementation
To illustrate this pattern, we prepare a markdown file that outlines the pattern's description:
The ObserverExample class contains a list of StockSubscriber and StockTickerModel objects, which represent specific stock tickers along with their subscription status.
The StockSubscriberSelection widget allows easy changes to specific subscriber classes, while the StockTickerSelection widget facilitates subscribing and unsubscribing from particular stock tickers at runtime.
As demonstrated, the subscription type can be modified at runtime, enabling you to start or cease tracking specific stocks whenever necessary.
All code modifications related to the Observer design pattern and its example implementation can be found in the provided resources.
Additional Articles in This Series
0 — Introduction 1 — Singleton 2 — Adapter 3 — Template Method 4 — Composite 5 — Strategy 6 — State 7 — Facade 8 — Interpreter 9 — Iterator 10 — Factory Method 11 — Abstract Factory 12 — Command 13 — Memento 14 — Prototype 15 — Proxy 16 — Decorator 17 — Bridge 18 — Builder 19 — Flyweight 20 — Chain of Responsibility 21 — Visitor 22 — Mediator
Your Contribution
If you found this article valuable, please consider showing your support by clicking the clap button below to encourage me to produce even better content!
Feel free to leave your thoughts, comments, or suggestions regarding this article.
Share this post with your peers on social media, and don’t forget to follow me on Medium.
Also, check out the GitHub repository for more resources.