This is the continuation of the Design Patterns series. In this post I am going to demonstrate the Observer Design Pattern.Observer is a behavioural design pattern in which an object notifies its dependents of the state changes.
Problem Statement
Assume that we are building a highly sophisticated financial Portfolio management system. We need to provide users the facility to build a portfolio to manage their stocks. The users should be able to add new stocks to their existing stocks. They should also be able to remove stocks from the portfolio. We need to show different representations of the data. The users should be shown the data in grid format as well as graphical format in the form of charts.
Solution
Similar to the previous demos related to design patterns, I am not going to build any user interface for this post as well. Assume that the system has a full blown user interface which has a parent window containing all the jazzy UI elements like grid, graph, menu bar, toolbar, buttons etc. I’ll concentrate only on the classes in the context. If you wish you can build a UI and integrate the classes defined here.
Assume that the user can add stock details. They also have the ability to remove a stock. Based on the user actions, there will be a Grid which will get updated. Within the windows, there is also graphical representation of the data showing all the stocks as a pie chart grouped by the sector. This provides the user a summarized view of the portfolio and their exposure by the sector in which they have invested. So lets look at some of the classes we have in the solution. We’ll start with the Stock class which acts as the Model for our application.
public class Stock
{
public string Name { get; set; }
public int Price { get; set; }
public Sector Sector { get; set; }
}
Currently the Stock class has only 3 properties. Name, Price and Sector. Sector is an enum which can be of following types:
- Banking
- Automobile
- IT
- Minimg
Next we look at the PortfolioManager class
public class PortfolioManager
{
private readonly IList<Stock> stocks;
private readonly StockGrid grid;
private readonly PieChart pieChart;
public PortfolioManager()
{
stocks = new List<Stock>();
grid = new StockGrid();
pieChart = new PieChart();
}
public ReadOnlyCollection<Stock> Stocks
{
get
{
return new ReadOnlyCollection<Stock>(stocks);
}
}
public void AddStock(Stock stock)
{
stocks.Add(stock);
grid.UpdateGrid(Stocks);
pieChart.DrawChart(Stocks);
}
public void RemoveStock(Stock stock)
{
stocks.Remove(stock);
grid.UpdateGrid(stocks);
pieChart.DrawChart(Stocks);
}
}
This is somewhat similar to a view model type of a class. This class provides the core functionality of adding and removing the stocks. There are two hypothetical classes named StockGrid and PieChart which we can think of as the ones which render the grid and the pie chart from the stocks data. Note that we call the UpdateGrid and DrawChart methods every time we add or remove a stocks from the portfolio.
Twist in the Tale
The users are very happy with the functionality and now wish to enhance it. They want to have other views of the portfolio. Along with the pie chart which displays the users exposure by Sector, they wish to visualize the portfolio as a Bar chart. The bars will be plotted against the Price of the Stock. Apart from the bar graph, the users also want to see the summary of stocks which displays the name and price of the highest and lowest stock.
We can refactor the above solution to make calls to two other class methods. We can define one class which displays the summary information. And another class which renders the bar chart. These two calls will need to be made from AddStock and RemoveStock methods. Looking back at this approach, it doesn’t look very good. If we look carefully, the underlying data is the same for all the different views. It is just that the different views needs to be updated when the Stocks collection is modified either by adding new stock or removing an existing stock.
The current approach makes the class holding the stocks collection dependent on the different views. The data and the representation of the data in the form of a grid, graphs and summary view are tightly coupled together. Imagine a situation where user has a dynamic screen and can choose from different views of the same data. In the same example, imagine that the user has finer control over the UI elements. He or she can choose what views are displayed in the window. Some users might choose to see only Pie chart and the Grid. Others might choose to add Grid, Pie chart, bar chart and summary view to their window. The combination can be anything as per the users choice.
In this scenario we will have to build some means of identifying if the Grid is chosen by the user or the Pie chart is chosen by the user. If the user has added a type of view like Grid to their UI then we need to call the respective method to update the current state of the stocks. In its current state the solution doesn’t look to scale well. What if we need another view of the stocks like a 3D chart. With every view that needs to be displayed in the UI we will have to have a reference in the portfolio manager class and invoke its respective method. If we could separate the two concerns it would be great.
Refactored Solution
Lets see how we can try and minimize the impact of the changes. How can we make sure that different views can be updated when the Stocks collection from PortfolioManager is modified? If we look at the methods of the PieChart and Grid class, they both depend on the current stocks collection. So we know that this is the data which all the different representations of the data will depend upon. So we can unify this and create an interface.
public interface IStockObserver
{
void Update(ReadOnlyCollection<Stock> stocks);
}
This interface has just one method Update which takes the Stocks collection as an input parameter. This simple interface makes our job easier within the PortfolioManager class. Instead of creating instances of each types like Grid or PieChart that depend on the stocks, we can depend on the abstraction. Lets see how this impacts our PortfolioManager.
We want to notify all the interested parties when the stock is added or removed. So lets define a method called Notify in our PortfolioManager class. This method is responsible for notifying all the interested parties when the state of the stocks collection changes.
public void AddStock(Stock stock)
{
stocks.Add(stock);
Notify();
}
public void RemoveStock(Stock stock)
{
stocks.Remove(stock);
Notify();
}
Here are our AddStock and RemoveStock methods. But how do we actually notify the interested receivers. For that we maintain a list of interested parties who would like to be notified about the state changes. The Notify method simply iterates over the collection of interested receivers and calls the Update method on them.
private void Notify()
{
foreach (IStockObserver stockObserver in stockObservers)
{
stockObserver.Update(Stocks);
}
}
That also looks straightforward. So the next question is how do we populate the stockObservers collection. We expose a method RegisterStockObserver which allows the interested observers to register themselves with the PortfolioManager class.
public void RegisterStockObserver(IStockObserver stockObserver)
{
stockObservers.Add(stockObserver);
}
That completes our changes to the PortfolioManager class. One thing that is still not clear is how does the individual receivers like the Grid and PieChart respond to this change notification. As many of you would have guessed, its by implementing the IStockObserver interface. Here is the implementation of StockGrid class.
public class StockGrid : IStockObserver
{
public void UpdateGrid(ReadOnlyCollection<Stock> stocks)
{
}
public void Update(ReadOnlyCollection<Stock> stocks)
{
UpdateGrid(stocks);
}
}
Similar is the case with PieChart class.
public class PieChart : IStockObserver
{
public void DrawChart(ReadOnlyCollection<Stock> stocks)
{
}
public void Update(ReadOnlyCollection<Stock> stocks)
{
DrawChart(stocks);
}
}
Both these classes implement the IStockObserver interface. The update method delegates the call to the UpdateGrid and DrawChart methods in respective classes. If you wish you can implement the code directly within the Update method itself. I’ll leave the implementation of the BarChart and Summary views to you as an exercise.
Now we have the class which Notifies and other classes which can do some processing with the changed stocks. But we still haven’t linked the two pieces together. In our previous code, all the class instances were available within the PortfolioManager itself. As a result we could call the respective methods on the instances of StockGrid and Piechart to update them.
After the refactoring we need some additional work to link the PortfolioManager and the dependent classes together. Assume that we have some sort of boots trapper or a shell kind of class which instantiates these classes and links them together.
public class Shell
{
public Shell()
{
PortfolioManager portfolioManager = new PortfolioManager();
StockGrid grid = new StockGrid();
PieChart pieChart = new PieChart();
portfolioManager.RegisterStockObserver(grid);
portfolioManager.RegisterStockObserver(pieChart);
portfolioManager.AddStock(new Stock());
}
}
Here we create a Shell class which links all the pieces together. We create instances of PortfolioManager, StockGrid and PieChart classes. Then we register the gird and pie chart as observers with the PortfolioManager. And finally add a new stock. You can verify by running this code that both the grid as well as pie chart gets notified.
Conclusion
This was the demonstration of the classic Gang of Four (GoF) implementation of the Observer Design Pattern. We can use the terminology described in GoF book to relate to the classes we have in our refactored solution. The PortfolioManager is know as the Subject. It is the class which contains the state information. The StockGrid and the PieChart are called the Observers. There are the classes interested in the changes to the state of the subject. the Observer pattern is also know as Publisher Subscriber pattern.
We can use Observer to remove the tight coupling between the subject and its observers. In our case we can add any number of observer going forward. There are built in features within DotNet framework which make it even more easier to implement the Observer patter. I’ll talk about those in near future.
As always the complete solution is available for download ObserverDesignPatternDemo.zip.
Until next time Happy Programming.
Further Reading
Here are some books I recommend related to the topics discussed in this post.
No comments:
Post a Comment