Understanding and Implementing the Observer Pattern in C#

Understanding and Implementing the Observer Pattern in C#


Introduction

This article introduces the basics of the Observer Pattern, when to use it, and how to implement it in C#.

Background

Often, we need one part of an application to be updated based on the status of another part. One way to achieve this is by having the receiver repeatedly check the sender for updates. However, this approach has two main problems:

  1. It consumes excessive CPU time as it continuously checks for new status.
  2. Depending on the interval at which we check for changes, we might not receive updates "immediately."

The solution to this problem is the Observer Pattern. While many of you may have seen numerous articles on this topic, including hundreds on CodeProject alone, I believe it’s worth sharing. It could still be useful for beginners, and the valuable comments I receive will help me learn more.

Here’s a class diagram of the Observer Pattern (Reference: GoF Design Patterns):


Using the Code

Let’s now discuss the classes involved, one by one:

  • Subject: This class keeps track of all the observers and provides the functionality to add or remove them. It is also responsible for notifying the observers whenever a change occurs. In our solution, we have ASubject implemented for this purpose.
  • ConcreteSubject: This class implements the Subject. It is the entity whose changes will affect other objects. In our implementation, we use the DummyProduct class for this purpose.
  • Observer: This represents an interface that defines the method that should be called whenever there’s a change. We have implemented this as IObserver.
  • ConcreteObserver: This is the class that needs to stay updated with changes. It simply implements the Observer interface, registers itself with the ConcreteSubject, and receives updates. We have the Shop class serving this role.

Here is the same diagram I’ve shown above, adapted for our implementation:


Before we move on to the code, I’d like to point out something important. In .NET, delegates are a great example of the Observer Pattern. We don’t necessarily need to implement the pattern fully in C# as delegates can provide the same functionality. However, we’ve implemented it here to understand the pattern better. Additionally, we’ve shown the delegate-based implementation of the Observer Pattern as well. Let’s now look at the code.

The Subject: ASubject

abstract class ASubject
{
    // This is one way to implement the observer pattern, let’s call it WAY_1
    ArrayList list = new ArrayList();

    // This is another way to implement the observer pattern, let’s call it WAY_2
    public delegate void StatusUpdate(float price);
    public event StatusUpdate OnStatusUpdate = null;

    public void Attach(Shop product)
    {
        // For WAY_1, we attach observers to the subject
        list.Add(product);
    }

    public void Detach(Shop product)
    {
        // For WAY_1, we detach observers from the subject
        list.Remove(product);
    }

    public void Attach2(Shop product)
    {
        // For WAY_2, we attach observers using the event
        OnStatusUpdate += new StatusUpdate(product.Update);
    }

    public void Detach2(Shop product)
    {
        // For WAY_2, we detach observers from the event
        OnStatusUpdate -= new StatusUpdate(product.Update);
    }

    public void Notify(float price)
    {
        // For WAY_1, notify observers of the change
        foreach (Shop p in list)
        {
            p.Update(price);
        }

        // For WAY_2, notify observers via event
        if (OnStatusUpdate != null)
        {
            OnStatusUpdate(price);
        }
    }
}        

The ConcreteSubject: DummyProduct

class DummyProduct : ASubject
{
    public void ChangePrice(float price)
    {
        Notify(price);
    }
}        

The Observer: IObserver

interface IObserver
{
    void Update(float price);
}        

The ConcreteObserver: Shop

class Shop : IObserver
{
    // Name of the product
    string name;
    float price = 0.0f; // Default price

    public Shop(string name)
    {
        this.name = name;
    }

    #region IObserver Members

    public void Update(float price)
    {
        this.price = price;

        // Just print to the console to test the update
        Console.WriteLine(@"Price at {0} is now {1}", name, price);
    }

    #endregion
}        

Testing the Code

static void Main(string[] args)
{
    DummyProduct product = new DummyProduct();

    // We have four shops that want to stay updated with the price set by the product owner
    Shop shop1 = new Shop("Shop 1");
    Shop shop2 = new Shop("Shop 2");
    Shop shop3 = new Shop("Shop 3");
    Shop shop4 = new Shop("Shop 4");

    // Using WAY_1 for the first two shops
    product.Attach(shop1);
    product.Attach(shop2);

    // Using WAY_2 for the other two shops
    product.Attach2(shop3);
    product.Attach2(shop4);

    // Change the product's price, which should automatically update the shops
    product.ChangePrice(23.0f);

    // Now, shop2 and shop4 are no longer interested in the new prices, so they unsubscribe
    product.Detach(shop2);
    product.Detach2(shop4);

    // Change the product's price again
    product.ChangePrice(26.0f);

    Console.Read();
}        

Points of Interest

The heart of Windows Forms programming and WPF applications is the Observer Pattern. Since the event-driven mechanism can only be facilitated by implementing the Observer Pattern, I wrote this basic article to explore it. It may not be very useful to advanced readers, but I enjoyed learning and writing it.

要查看或添加评论,请登录

Rahul Singh的更多文章

社区洞察

其他会员也浏览了