Observer Design Pattern With Delegates And Events

In the previous post, I had demonstrated the Observer Design Pattern. The approach I showed is similar to the classic approach described in Gang Of Four (GoF) book. At the time of writing GoF book C++ was one of the most commonly used language. Over the period of time, different variants or derivatives of C++ languages emerged like Java and C#. These languages have built in support for many of the classic design patterns described in GoF. We tend to use these features baked into the language without even realizing that its an implementation of the design pattern. In todays post, which is a continuation of Design Patterns series, I am going to demonstrate the C# adaptation of Observer Design Pattern using Delegates and Events.

Problem Statement

There is no change in the problem statement which we had used in the last post. The requirement is exactly the same as it was before. I am going to refactor the solution from the previous post into the one which uses the C# language features like Delegates and Events. So lets get started.

Refactored Solution using Delegates & Events

In the previous post, we had used an IStockObserver interface to define the method signature that the Observers were going to use. Similar feature can be implemented  without using an interface. We can define a Delegate which defines the method signature that the observers should be using. Any method which has the same signature can be used as an observer. Here is the refactored version of the PortfolioManager class.

    internal class PortfolioManager

    {

        private readonly IList<Stock> stocks;

 

        public PortfolioManager()

        {

            stocks = new List<Stock>();

 

            // stockObservers = new List<IStockObserver>();

        }

 

        public delegate void StocksChangeHandler(ReadOnlyCollection<Stock> stocks);

 

        public event StocksChangeHandler StocksChanged;

 

        public ReadOnlyCollection<Stock> Stocks

        {

            get

            {

                return new ReadOnlyCollection<Stock>(stocks);

            }

        }

 

        // public void RegisterStockObserver(IStockObserver stockObserver)

        // {

        //    stockObservers.Add(stockObserver);

        // }

 

        public void AddStock(Stock stock)

        {

            stocks.Add(stock);

 

            // Notify();

            OnStocksChanged(Stocks);

        }

 

        public void RemoveStock(Stock stock)

        {

            stocks.Remove(stock);

 

            // Notify();

            OnStocksChanged(Stocks);

        }

 

        protected void OnStocksChanged(ReadOnlyCollection<Stock> currentStocks)

        {

            if (StocksChanged != null)

            {

                StocksChanged(currentStocks);

            }

        }

 

        //private void Notify()

        //{

        //    foreach (IStockObserver stockObserver in stockObservers)

        //    {

        //        stockObserver.Update(Stocks);

        //    }

        //}

    }

Note the lines just after the constructor. We declare a delegate StocksChangedHandler and an event named StocksChanged. As a result of this refactoring, we no longer need to maintain the list of IStockObserver in our PortfolioManager. All the plumbing is done by the framework through delegate. When the state changes, we can notify the observers on subscribers by invoking the delegate method. For easier comparison I have commented the code from previous blog.

We have replaced the Notify method from the previous implementation with a method named OnStocksChanged. This method checks if there are any subscribers for the StocksChanged event and invokes the delegate methods on those subscribers. With the refactoring the number of lines seem to remain the same but responsibility as to how we notify the changes in state gets affected slightly. Lets look at the subscription side of things.

    public class Shell

    {

        public Shell()

        {

            PortfolioManager portfolioManager = new PortfolioManager();

 

            StockGrid grid = new StockGrid();

 

            PieChart pieChart = new PieChart();

 

            // portfolioManager.RegisterStockObserver(grid);

            portfolioManager.StocksChanged += grid.Update;

 

            //portfolioManager.RegisterStockObserver(pieChart);

            portfolioManager.StocksChanged += pieChart.Update;

 

            portfolioManager.AddStock(new Stock());

 

            portfolioManager.StocksChanged -= pieChart.Update;

 

            portfolioManager.RemoveStock(portfolioManager.Stocks[0]);

        }

    }

There is not much change in the subscription part. Instead of registering the observer using a dedicated method, we use the C# language syntactical sugar to subscribe to the StocksChanged event. Earlier we used to pass the complete object like pieChart or grid. Now we just register the name of the method which matches the delegate method signature.

The last two lines are additional ones which I have added to show how we can unsubscribe for pieChart at runtime. If you imagine a GUI related app, this might be something like simulating a close of a widget which displays the pie chart component in the GUI. Finally we look at the observer classes.

    public class StockGrid

    {

        public void UpdateGrid(ReadOnlyCollection<Stock> stocks)

        {

            Console.WriteLine("Update grid called...");

        }

 

        public void Update(ReadOnlyCollection<Stock> stocks)

        {

            UpdateGrid(stocks);

        }

    }

As we see in this version there is no need for observer to implement the IStockObserver interface. We just need a method matching the signature of the delegate. In fact we can get rid of the delegation in the above code and directly register UpdateGrid as the subscriber for the event as it matches the delegate signature. I’ll leave it as an exercise for the reader to try it out.

Conclusion

As we saw from the refactored code, C# language has built in support for Observer Pattern. Not just C# other DotNet framework based languages like VB.Net etc also support delegates and events. Having build in support within the language saves developers from writing boilerplate code. I personally feel that this is a better approach compared to abstracting the Observer interface as shown in the earlier post.

As always, the complete working solution is available for download demo.zip.

Until next time happy Programming.

Further Reading

Here are some books I recommend related to the topics discussed in this blog.

Share:
spacer

No comments:

Post a Comment