Observer Pattern with concrete example

Understanding the Observer Pattern

The Observer Pattern is a behavioral design pattern that defines a one-to-many dependency between objects. In simpler terms, when one object (the subject) changes its state, all its dependents (observers) are notified and updated automatically. This pattern promotes a loosely coupled architecture, fostering scalability and ease of maintenance.

The Anatomy of Observer Pattern

  1. Subject:
    • The entity being observed, often encapsulating a state or data of interest.
    • Maintains a list of observers and notifies them of any state changes.
  2. Observer:
    • The interface or abstract class that defines the update method.
    • Concrete observer classes implement this method to respond to state changes.

Concrete Case: Real-time Stock Market Updates

Let’s delve into a practical example to grasp the Observer Pattern’s implementation. Consider a real-time stock market monitoring system, where multiple investors want to stay informed about the latest changes in the stock prices.

The subject : StockMarket


public class StockMarket
{
    private double _currentPrice;
    private List<IInvestor> _investors = new List<IInvestor>();

    public double CurrentPrice
    {
        get { return _currentPrice; }
        set
        {
            _currentPrice = value;
            NotifyInvestors();
        }
    }

    public void AddInvestor(IInvestor investor)
    {
        _investors.Add(investor);
    }

    public void RemoveInvestor(IInvestor investor)
    {
        _investors.Remove(investor);
    }

    private void NotifyInvestors()
    {
        foreach (var investor in _investors)
        {
            investor.Update(_currentPrice);
        }
    }
}

The observer : StockInvestor

// Observer
public interface IInvestor
{
    void Update(double stockPrice);
}

// Concrete Observer
public class StockInvestor : IInvestor
{
    private string _name;

    public StockInvestor(string name)
    {
        _name = name;
    }

    public void Update(double stockPrice)
    {
        Console.WriteLine($"{_name} received an update: Stock price is {stockPrice}");
    }
}

Let’s simulate the behavior.

class Program
{
    static void Main(string[] args)
    {
        var stockMarket = new StockMarket();

        var investor1 = new StockInvestor("John");
        var investor2 = new StockInvestor("Alice");

        stockMarket.AddInvestor(investor1);
        stockMarket.AddInvestor(investor2);

        // Simulate stock price changes
        stockMarket.CurrentPrice = 150.0;
        stockMarket.CurrentPrice = 155.5;

        // Remove an investor
        stockMarket.RemoveInvestor(investor2);

        // Simulate more changes
        stockMarket.CurrentPrice = 152.8;
    }
}

The console output :

Using event-based approach

To improve the given code using events, we can introduce an event in the StockMarket class that investors can subscribe to. This approach allows the StockMarket to notify its investors about price changes without explicitly calling their Update method. Here’s how you can modify the code:

Step 1: Define an Event in StockMarket

First, define an event in the StockMarket class that will be triggered whenever the stock price changes. This event will be of type Action<double>, where the action takes a double parameter representing the new stock price.

public class StockMarket
{
    private double _currentPrice;
    private List<IInvestor> _investors = new List<IInvestor>();
    public event Action<double> PriceChanged; // New event

    public double CurrentPrice
    {
        get { return _currentPrice; }
        set
        {
            if (_currentPrice!= value)
            {
                _currentPrice = value;
                OnPriceChanged(value); // Trigger the event
            }
        }
    }

    public void AddInvestor(IInvestor investor)
    {
        _investors.Add(investor);
    }

    public void RemoveInvestor(IInvestor investor)
    {
        _investors.Remove(investor);
    }

    protected virtual void OnPriceChanged(double newPrice) // Method to trigger the event
    {
        PriceChanged?.Invoke(newPrice);
    }
}

Step 2: Modify IInvestor Interface

Since we’re moving away from the direct Update method call, the IInvestor interface no longer needs to have an Update method. However, we’ll keep it for backward compatibility if needed. We will replace it by the SubscribeToPriceChanges method.

public interface IInvestor
{
    void SubscribeToPriceChanges(StockMarket market);

}

Step 3: Implement the Event Subscription in StockInvestor

Now, each investor can subscribe to the PriceChanged event of the StockMarket. This way, they will be notified automatically whenever the stock price changes.

public class StockInvestor : IInvestor
{
    private string _name;

    public StockInvestor(string name)
    {
        _name = name;
    }

    public void SubscribeToPriceChanges(StockMarket market)
    {
        market.PriceChanged += (newPrice) => Console.WriteLine($"{_name} received an update: Stock price is {newPrice}");
    }
}

Step 4: Update the Main Method

Finally, update the Main method to subscribe the investors to the PriceChanged event.

var stockMarket = new StockMarket();

        var investor1 = new StockInvestor("John");
        var investor2 = new StockInvestor("Alice");

        stockMarket.AddInvestor(investor1);
        stockMarket.AddInvestor(investor2);

        // Subscribe investors to price changes
        investor1.SubscribeToPriceChanges(stockMarket);
        investor2.SubscribeToPriceChanges(stockMarket);

        // Simulate stock price changes
        stockMarket.CurrentPrice = 150.0;
        stockMarket.CurrentPrice = 155.5;

        // Remove an investor
        stockMarket.RemoveInvestor(investor2);

        // Simulate more changes
        stockMarket.CurrentPrice = 152.8;

This refactoring introduces the event-based approach, allowing for a cleaner separation of concerns between the StockMarket and its investors. Investors now subscribe to receive notifications about price changes, making the system more flexible and easier to extend.

Conclusion

The Observer Pattern empowers developers to create systems where objects communicate seamlessly without tight coupling. By employing this pattern, we enhance the extensibility and maintainability of our code, ensuring that changes in one part of the system do not lead to a cascade of modifications elsewhere.
Introducing the event-based approach significantly improves the design of the StockMarket system by enhancing modularity, flexibility, scalability, and ease of extension. It also simplifies testing by clearly separating the responsibilities of different components.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *