Home Simplify Observer (Behavioural Pattern)
Post
Cancel

Simplify Observer (Behavioural Pattern)

🌑 Why Observer Pattern

Architect Spidey continues his journey in shaping the Movie Review Application, introducing an engaging new feature — Live Review Updates. This enhancement ensures that users always see the latest movie reviews. In the movie list, a subtle indicator shows on each row when new reviews arrive. In the detail screen, a banner at the top alerts the user in real-time.

The application already has a centralised ReviewTracker service responsible for fetching and managing review updates. However, the current setup relies on each screen actively checking the service at intervals to detect new reviews. This approach works, but it’s not ideal — it’s manual, inefficient, and prone to missed or delayed updates.

Spidey remembers how, when she was a child, she loved to play during study time—especially when her father wasn’t at home. But instead of running to check the main door repeatedly, she had a smart idea. She would quietly lock the house’s main door. The moment she heard the creak of the gate or the jingle of keys, she knew it was time to stop playing and get back to studying.

She didn’t keep checking the door. She waited and listened. That’s what the Observer Pattern helps you build: A way to react when something changes, without constantly checking.

🌥️ Outline

The Observer Pattern lets one object (called the publisher) keep track of important data. When that data changes, it notifies all interested observers without requiring them to request it.

‘Publisher – I’ll let you know when something happens.’
‘Observer – I’m listening, tell me when there’s news.’

🌜 Observer In Real World

When someone rings the doorbell, everyone in the house hears it and reacts. No one in the house needs to keep checking the door to see if someone has arrived. They observe and respond when the sound comes.

When the bell rings (a state change), all observers are notified automatically.

🌕 Solution

Spidey defines a Publisher interface to maintain a list of all observers who are interested in updates. Then, a concrete class called ReviewPublisher implements this interface, handling the logic of adding, removing, and notifying observers whenever a new review is available. Publisher remains simple: just managing a list of listeners and notifying them on changes.

Then, the application already had a centralised ReviewTracker for managing review updates. Now, Spidey improves it by integrating the Observer pattern. Instead of requiring screens to check for updates, ReviewTracker now notifies them when new reviews are available, just like listening for a doorbell instead of constantly checking the door.

Further, when the MovieListScreen or MovieDetailScreen appears on the screen, it registers itself as an observer to the ReviewPublisher through the ReviewTracker. This means it’s letting the publisher know, “I’m interested. Please inform me when something new happens.”

From that point onward, whenever the ReviewTracker detects a new review, it simply notifies the ReviewPublisher. The publisher, in turn, calls each registered screen’s update method, making sure all observers reflect the latest changes instantly.

Observer Components

Publisher Interface:
It defines a clear contract for how any publisher should behave. Specifically, it provides methods to:

    Add an observer – when someone wants to start listening for updates.
    Remove an observer – when someone no longer wishes to be notified.
    Notify observers – when something changes, and all listeners need to be informed.

The Publisher protocol acts as this interface — a formal agreement on how communication should happen.

Concrete Publisher:
This is the actual working class that implements the Publisher interface. It keeps a list of observers and informs them when necessary. The ReviewPublisher is your Concrete Publisher — the one who carries the responsibility of alerting others whenever a new movie review is available.

Observer Interface:
It defines a clear contract of what every observer must do — how they receive an update. The Observer protocol is this interface, ensuring every listener knows how to respond when something changes. In above example: didReceiveNewReview method – a single method that gets called when the publisher sends out a notification.

Concrete Observer:
These are the actual components for which we would like to receive updates. They implement the Observer interface and define exactly what should happen when an update is received. Both MovieListScreen and MovieDetailScreen act as Concrete Observers — they register themselves with the publisher and react appropriately when a new review arrives.

Optional Components

Mediator:
In most cases, a single object is responsible for both managing the observers and maintaining the state that needs to be updated. To maintain cleanliness, Spidey introduces a mediator — a component that serves as a bridge between the publisher and the rest of the application. In this setup, ReviewTracker acts as that mediator — fetching review updates and delegating notifications to the ReviewPublisher.

🌟 Where To Apply

   For scalable, event-driven communication
The Observer Pattern establishes a one-to-many relationship between objects, allowing multiple observers to stay in sync with a single subject (or publisher).
When the subject’s state changes, all subscribed observers are automatically notified. It helps keep components loosely coupled to the source of truth, promoting a clean separation of concerns between the logic that produces the updates and the components that respond to them. Typical use cases include UI updates, event broadcasting, and notification systems.

   For Real-Time Reactions
The Observer Pattern is ideal when you want different parts of your app to react instantly to timely changes, without the need for constant polling or manual refresh. Observers stay subscribed to a subject, and whenever the subject updates its state, they are notified in real time.
Example: Movie reviews are updated in real-time. When a new review is posted, the ReviewPublisher notifies all registered observers. If the Movie List Screen is visible, a small indicator appears beside the movie. If the Movie Detail Screen is open, a real-time banner appears at the top to alert the user.

🌓 To Gain Something, You Must Trade Off

Design patterns are solutions to common problems in software design. They provide a structured approach to solving issues, much like a blueprint. While it’s possible to solve a problem without using a design pattern, applying one offers better flexibility, scalability, and maintainability. However, each benefit comes with its trade-off. Let’s examine this concept in the context of the Observer Pattern.

Flexibility vs Complexity

The Observer Pattern gives you great flexibility — components can respond to changes without knowing about each other. Also, observers can be added or removed at runtime.
However, this flexibility adds complexity. Observers must be properly managed throughout their lifecycle. If not removed at the right time, they can cause memory leaks or trigger updates even after a screen is gone. This demands discipline in implementation, especially in UI-heavy apps.

Maintainability vs Debugging

The Observer Pattern helps keep your codebase maintainable by decoupling components. Observers don’t need to know about the internals of the state holding object — they just “listen” for changes. This separation promotes reusability, testability, and a cleaner architecture.

But debugging can become tedious, as observers are often added quietly in the background. When something breaks — like an update that doesn’t reach the UI or arrives unexpectedly — it’s hard to trace the source. So while the pattern supports maintainability in design, it requires careful handling to avoid debugging nightmares in implementation.

Reactivity vs Control

The Observer Pattern forms the foundation of reactive architecture — where data flows automatically to wherever it’s needed, and UI components update as soon as the underlying state changes. It’s a beautiful shift from imperative code to declarative, event-driven design.

However, this reactive flow comes with trade-offs. You give up fine-grained control in favour of responsiveness. like prioritising which observer should respond first, or pausing, filtering, or batching notifications without adding extra layers of coordination.

🌚 Some Interesting Facts

   Observer & Command Pattern
These two patterns often get confused, especially because both can involve callbacks or triggering actions, but their purposes are quite different.
The Observer allows objects to respond automatically when something changes, without needing to constantly check or poll. It’s like a bell that rings when someone comes — you simply respond when you hear it.
On the other hand, the Command Pattern encapsulates a task that needs to be performed. Each task is wrapped in a class, allowing you to flexibly queue, log, undo, or execute tasks, giving you full control over when and how they are performed. It’s like pulling a lever — the task is predefined and stored, ready to be executed when someone pulls the lever.

‘Observer: Just let me know when something happens.’
‘Command: Encapsulate actions to execute.’

   Observer & Mediator Pattern
Both patterns help reduce tight coupling between components, but they do so in different ways. Mediator acts as a central object that manages interactions between different components. Objects don’t talk to each other directly; they communicate through a mediator, who routes requests and keeps components decoupled.
Observer, on the other hand, is about reacting to state changes. Observers subscribe to a subject and are notified automatically when that subject changes, allowing for reactive updates without manual polling or tight connections.

‘Mediator – Talk to me, I’ll handle the rest.’
‘Observer – React to state changes.’

    Observer Helps Keep Features Modular and Extensible
The Observer Pattern naturally encourages a modular system design. Instead of tightly coupling components, it allows you to separate core logic from the responses to changes.
By using this pattern:
🔆     New features can be added as observers without touching existing logic.
🔆     Different modules can evolve independently as long as they conform to the observer interface.
🔆     Testing individual parts becomes easier, since dependencies are minimal.

Like multiple appliances can plug into the same point, the Observer Pattern allows each component (such as a radio, lamp, or monitor) to be built independently, only needing to conform to a known interface (the plug or the observer interface). The main system (subject) doesn’t need to know the specifics of who’s listening — it simply sends out updates. New appliances (observers) can be added without touching the socket (subject) itself, supporting easy extensibility.

🧘 Final Thoughts

Spidey feels the Observer Pattern makes the code modular, reactive, and easier to maintain — letting parts of the app stay updated without constant checking, just like listening for a bell instead of peeking at the door.

Code Example (Swift)

Publisher Interface:

1
2
3
4
5
6
7
protocol Publisher {
    associatedtype ObserverType

    func addObserver(_ observer: ObserverType)
    func removeObserver(_ observer: ObserverType)
    func notifyObservers(for movieID: String)
}

Concrete Publisher:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ReviewPublisher: Publisher {
    typealias ObserverType = ReviewObserver

    private var observers: [ReviewObserver] = []

    func addObserver(_ observer: ReviewObserver) {
        observers.append(observer)
    }

    func removeObserver(_ observer: ReviewObserver) {
        observers.removeAll { $0 === observer }
    }

    func notifyObservers(for movieID: String) {
        for observer in observers {
            observer.didReceiveNewReview(for: movieID)
        }
    }
}

Observer Interface:

1
2
3
protocol ReviewObserver: AnyObject {
    func didReceiveNewReview(for movieID: String)
}

Concrete Observer:

1
2
3
4
5
class MovieListScreen: ReviewObserver {
    func didReceiveNewReview(for movieID: String) {
        print("🟡 MovieListScreen: Show update indicator for movie \(movieID)")
    }
}

Mediator:

1
2
3
4
5
6
7
8
9
class ReviewTracker {
    static let shared = ReviewTracker()
    let publisher = ReviewPublisher()

    func receivePushNotification(for movieID: String) {
        print("📩 Push received for movie ID: \(movieID)")
        publisher.notifyObservers(for: movieID)
    }
}

Client Code:

1
2
3
let listScreen = MovieListScreen()
let tracker = ReviewTracker.shared
tracker.publisher.addObserver(listScreen)
This post is licensed under CC BY 4.0 by the author.

Simplify Strategy (Behavioural Pattern)

From Chaos to Clarity - Using TaskGroup Instead of DispatchGroup

Comments powered by Disqus.