The Observer Design Pattern in C#

The Observer design pattern is a behavioral pattern and was one of the original design patterns defined by the Gang of Four (GoF). The Observer pattern defines a relationship between a publisher, also known as a subject, and one or more subscribers, also known as observers. In this post, I will describe the pattern and show a sample implementation.

What Problem Does It Solve?

The Observer design pattern is very useful for situations that require push-based notifications. The subject doesn’t need to know about the implementation of the observers, it just needs to provide a way for them to subscribe and then notify the observers when some predefined condition, event, or state change occurs.

The Actors

For this post, I will create a fictional but simple price watching system. The system will only contain a single product and, using a timer, I will create random price fluctuations that will cause the price to either go up or down. I will also create a pair of observers with thresholds that will either buy or sell their stock based on price thresholds.

Subject – PriceWatcher

This class has three very important methods: AddObserver, RemoveObserver, and Notify. These three methods form the substance of the Observer pattern which is to manage the observers and notify them that something has changed. The rest will be completely different in every application but those three methods will be somewhat the same each time this design pattern is implemented.

Listing 1.1 – PriceWatcher.cs

public class PriceWatcher
{
    private Timer _timer = null;
    private double _currentPrice = 20d;
    private bool _direction = false;
    private List<IPriceObserver> _observers = null;

    public PriceWatcher()
    {
        _observers = new List<IPriceObserver>();

        _timer = new Timer(1006);
        _timer.Elapsed += TriggerPriceChange;
    }

    public void Start()
    {
        _timer.Start();
    }

    public void Stop()
    {
        _timer.Stop();
    }

    public void AddObserver(IPriceObserver observer)
    {
        _observers.Add(observer);
    }

    public void RemoveObserver(IPriceObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Notify()
    {
        foreach(IPriceObserver observer in _observers)
        {
            observer.PriceChanged(_currentPrice);
        }
    }

    private void TriggerPriceChange(Object source, ElapsedEventArgs args)
    {
        _direction = !_direction;

        Random changeRandom = new Random(System.DateTime.Now.Millisecond);
        int change = changeRandom.Next(50);

        double priceChange = (double)change / (double)100;

        if (_direction)
        {
            if (_currentPrice - priceChange > 18.50d)
            {
                priceChange = priceChange * -1;
            }
        }
        else
        {
            if (_currentPrice + priceChange > 21.50d)
            {
                priceChange = priceChange * -1;
            }
        }

        if (priceChange != 0.00d)
        {
            _currentPrice = _currentPrice + priceChange;

            Console.WriteLine($"Price change triggered for {priceChange:c}. Current Price: {_currentPrice:c}");
        }

        Notify();
    }
}

Observer Contract – IPriceObserver

This interface forms the contract that all other PriceObservers must be implemented to be used by the PriceWatcher class. The implementation is very simple as the only real concern is that the PriceWatcher class must have some way to notify its observers.

Listing 1.2 – IPriceObserver.cs

public interface IPriceObserver
{
    void PriceChanged(double currentPrice);
}

Observer – PriceObserver

A rather simple implementation of the IPriceObserver interface. It monitors the price and if the price goes below a specific threshold, it will buy an item and if the price goes above another threshold, it will sell an item.

Listing 1.3 – PriceObserver.cs

public class PriceObserver : IPriceObserver
{
    private int _stockCount = 0;
        
    public void PriceChanged(double currentPrice)
    {
        UpdateStock(currentPrice);
    }

    private void UpdateStock(double currentPrice)
    {
        if(currentPrice > 20.5 && _stockCount > 0)
        {
            _stockCount--;
            Console.WriteLine($"Price Observer sold 1 item. Current Stock: {_stockCount}");
        }
        else if(currentPrice < 19.5)
        {
            _stockCount++;
            Console.WriteLine($"Price Observer bought 1 item. Current Stock: {_stockCount}");
        }
    }
}

How to Implement

The implementation is rather simple. To get it wired up, you create an instance of the PriceWatcher object and then add an observer.

Listing 1.4 – Implementation

PriceWatcher watcher = new PriceWatcher();
watcher.AddObserver(new PriceObserver());
watcher.Start();

Conclusion

The observer design pattern is both very useful and very simple. In today’s connected world, the possible uses and implementations are nearly endless. Anytime a Subject’s state changes, the Observers would probably need to know about it. The Observer pattern forms a very simple and elegant solution to this problem.

Leave a Reply

Your email address will not be published.