3.1 - Observer Pattern: A Behavioral Design Pattern

In software design, patterns provide solutions to common problems that developers face while building applications. One such design pattern that helps with the communication between objects is the Observer Pattern, which falls under the Behavioral Design Patterns category.

In this blog, we’ll explore the Observer Pattern, how it works, and walk through an example implementation using the weather station analogy.

What is the Observer Pattern?

The Observer Pattern defines a one-to-many dependency between objects, where one object (the Subject) notifies other dependent objects (the Observers) about any state changes. This pattern is useful when the change in one object needs to be reflected in several others without creating a tightly coupled system.

Key Components:

  1. Subject: The object being observed. It maintains a list of observers and notifies them when its state changes.

  2. Observer: The objects that need to stay updated when the subject changes. They implement an update mechanism to receive notifications from the subject.

  3. Concrete Subject: The subject that sends updates when there are changes.

  4. Concrete Observer: The observer that performs actions when it receives updates from the subject.

Use Cases:

  • A weather station system where various displays (temperature, humidity, etc.) need real-time weather updates.

  • A stock market monitoring system where stock prices change and notify all investors of updates.

  • Notification systems where changes (e.g., new content, messages, etc.) notify subscribers.

Let’s take a weather station system as our example to illustrate the Observer Pattern.

Example: A Weather Station with Observers

We will simulate a Weather Station as the Subject, which will notify registered observers (like displays for different cities) whenever there’s a weather update.

Step 1: Define the Publisher Interface

The Publisher interface defines methods for adding, removing, and notifying subscribers.

// Publisher interface
interface NewsAgency {
    void addSubscriber(NewsSubscriber subscriber);
    void removeSubscriber(NewsSubscriber subscriber);
    void notifySubscribers(String news);
}

Step 2: Implement the Concrete Publisher

The CityNewsAgency class implements the NewsAgency interface. It maintains a list of subscribers and broadcasts news updates to them.

import java.util.ArrayList;
import java.util.List;

class CityNewsAgency implements NewsAgency {
    private List<NewsSubscriber> subscribers = new ArrayList<>();

    @Override
    public void addSubscriber(NewsSubscriber subscriber) {
        subscribers.add(subscriber);
    }

    @Override
    public void removeSubscriber(NewsSubscriber subscriber) {
        subscribers.remove(subscriber);
    }

    @Override
    public void notifySubscribers(String news) {
        for (NewsSubscriber subscriber : subscribers) {
            subscriber.receiveUpdate(news);
        }
    }

    // Method to publish new news and notify all subscribers
    public void publishNews(String newsUpdate) {
        notifySubscribers(newsUpdate);
    }
}

Step 3: Define the Subscriber Interface

The NewsSubscriber interface defines how subscribers receive news updates from the publisher.

// Subscriber interface
interface NewsSubscriber {
    void receiveUpdate(String news);
}

Step 4: Implement Concrete Subscribers

In this step, we create RegionalSubscriber as a concrete subscriber class that prints news updates for a specific region.

class RegionalSubscriber implements NewsSubscriber {
    private String region;

    public RegionalSubscriber(String region) {
        this.region = region;
    }

    @Override
    public void receiveUpdate(String news) {
        System.out.println("News for " + region + ": " + news);
    }
}

Step 5: Testing the Observer Pattern with Different Names

Now let’s create a main program to tie everything together.

public class NewsSystem {
    public static void main(String[] args) {
        // Create the news agency (publisher)
        CityNewsAgency newsAgency = new CityNewsAgency();

        // Create subscribers for different regions
        NewsSubscriber subscriber1 = new RegionalSubscriber("Region X");
        NewsSubscriber subscriber2 = new RegionalSubscriber("Region Y");

        // Add subscribers to the news agency
        newsAgency.addSubscriber(subscriber1);
        newsAgency.addSubscriber(subscriber2);

        // Publish news and notify all subscribers
        newsAgency.publishNews("New policies introduced today.");
    }
}

Output:

News for Region X: New policies introduced today.
News for Region Y: New policies introduced today.

Step 6: Removing a Subscriber

To demonstrate removing a subscriber, we remove one and publish another update.

newsAgency.removeSubscriber(subscriber1);
newsAgency.publishNews("Rain expected in the northern areas.");

Output:

News for Region Y: Rain expected in the northern areas.

Benefits of the Observer Pattern

  1. Loose Coupling: The subject and observers are loosely coupled. The subject doesn’t need to know the concrete implementation of the observers, which promotes flexibility and reusability.

  2. Dynamic Relationships: Observers can be added or removed at runtime, making the system dynamic and flexible.

  3. Efficiency: Observers only receive notifications when there is an update, making it more efficient in event-driven systems.

When to Use the Observer Pattern?

  • When a one-to-many relationship is needed between objects.

  • When you need to decouple an object from its dependents but still need them to stay in sync.

  • When changes in one part of the system should trigger updates in other parts.

Conclusion

The Observer Pattern implemented here with different class names and method names follows the same principles, making it a flexible and decoupled solution for maintaining multiple dependent objects in sync with one main object. Whether it's for weather updates or news distribution, the pattern can be adapted to suit various real-world applications.