Using the Observer Design Pattern in C# ASP .NET

modified

Introduction

Similar in nature to the Chain of Responsibility design pattern, the Observer pattern allows linking of several listener classes to a subject class. When a change or event occurs within the subject class, all listeners are made aware of the event and may take action. This essentially creates a one-to-many relationship between the subject and listeners and can be a powerful tool when designing your software with C#.

Observer Design Pattern in C# ASP .NET

Differences Between the Chain of Responsiblity and the Observer

The Chain of Responsibility pattern and the Observer pattern are similar in that they both provide a means of allowing multiple classes to handle an event. The Chain of Responsibility typically passes the event down the chain and once the event is handled, the chain ends. This results in a single class acting upon the event. In contrast, the Observer pattern passes the event to all listeners in the chain, who may each act as a result of the event. The core difference between the two patterns hinges on a for loop.

What Exactly is an Observer?

An observer is a class that watches another class for a signal or change in state. For example, an observer could watch a class’s title property. The moment the title changes, the observer would be invoked, perhaps logging the change. Coding a basic program to implement this can be made much more efficient with the Observer pattern. Let’s take a look at the basic main program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void Main(string[] args)
{
    ConcreteSubject MySubject = new ConcreteSubject();
    ConcreteObserver ObserverOne = new ConcreteObserver(MySubject, "One");
    ConcreteObserver ObserverTwo = new ConcreteObserver(MySubject, "Two");
    MySubject.Attach(ObserverOne);
    MySubject.Attach(ObserverTwo);
    MySubject.strText = "Hello World!";
    Console.ReadKey();
}

We begin by creating our subject class. We then create a few observer classes and pass them a reference of the subject along with a unique name. This allows the observers to have knowledge of the subject class and its properties. Finally, we attach the observers to the subject. The subject class itself contains event handling on its strText property. When the property is changed, it will invoke its Notify() function, which in turn, notifies each Observer that a change has occured. Depending on the functionality designed in the Observer class, it can be made to detect exactly what field has changed.

What’s the Subject?

Now, we’ll define the base class for our Subject classes. Since we will be filling in the function bodies for part of the class, our Subject base will be an abstract class, as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
abstract class Subject
{
private List<IObserver> observers = new List<IObserver>();
    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }
    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }
    public void Notify()
    {
        foreach (IObserver o in observers)
        {
            o.Update();
        }
    }
}

Our Subject base class contains an array list of observers. We allow adding and removing of the observers by calling the base class functions Attach and Detach. These functions simply manipulate the observer array. The base class also includes a Notify function, which iterates over the list of observers and calls their individual notification method, Update().

Also note that the Subject base class deals exclusively with IObserver objects. IObserver is an interface, providing a generic version of any Observer class. By using these generic observer objects in the bass class, we can avoid having to refer to a concrete Observer class. This enhances our Observer design pattern with loose coupling and extensibility.

Creating Our Own Subject

Now that the base class for an oberservable Subject has been designed, we can create an actual concrete subject class. For this example, our concrete subject will simply have a string variable. It will also notify its observers whenever its string variable is modified.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ConcreteSubject : Subject
{
    public string _strText;
    public ConcreteSubject(string aText)
    {
        strText = aText;
    }
    public string strText
    {
        get
        {
            return _strText;
        }
        set
        {
            _strText = value;               
            Notify();
        }
    }
}

Since the Subject base class contains the implementation functions for handling the Observers, there is very little code in our concrete subject that deals exclusively with the Observer design pattern. The one important line is the call to the base class function Notify(). This occurs when strText is modified, allowing all observers of this class to receive a notification.

Designing the Observer

With the subjects complete, we can now begin the Observer. All observers will derive from the same interface, IObserver.

1
2
3
4
interface IObserver
{
    void Update();
}

Notice that the only definition we include in the interface is a call to Update(). This is the function that must be called to notify an observer of an event. This is the same function called in the for loop by the Subject base class when it iterates over all of its observers to invoke an event.

An Observer With Some Body

Next, we create a concrete observer based off of our IObserver interface. We also fill in the interface’s method for Update(). In this example, our concrete observer will contain a Name (to identify it by), a string representing a copy of the Subject’s string data, and a reference to its subject.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ConcreteObserver : IObserver
{
    public string strName = "";
    public string strText = "";
    private ConcreteSubject subject;
    public ConcreteObserver(ConcreteSubject aSubject, string aName)
    {
        subject = aSubject;
        strText = subject.strText;
        strName = aName;
    }
    #region IObserver Members
    public void Update()
    {
        Console.WriteLine(strName + " detected a change from: " + strText + " to " + subject.strText);
        strText = subject.strText;
    }
    #endregion
}

Since the Update method is effectively a notification of an event from the Subject, we place any code to handle the event within this function (or called from this function). In our example, we simply write to the console that we’ve detected a change in the Subject. Since we hold a copy of the original string, we can write out the old and new values. Finally, we update our internal string value to the new value.

Putting it All Together

With the Subject and Observers defined, the above main program can now execute. The subject and observers are created. The observers are handed a reference of the subject and assigned names. We then attach the observers to the subject. When we change the value of the string property of the subject, both observers are alerted to the change and report this to the console.

Example Output

1
2
One detected a change from: Blank to Hello World!
Two detected a change from: Blank to Hello World!

Enhancing the Observer Design Pattern with .NET Threads

You may have noticed the Observer design pattern contains one bottleneck - the for loop. Unlike the Chain of Responsibility design pattern, which iterates across its chain until the event is handled (and often never reaches the end if a member handles the event beforehand), the Observer pattern always iterates across all members of its chain. This could produce costly time delays when processing data or iterating over tens or hundreds of observers on a single subject. Our main program could potentially wait for thousands of observers to finish being notified before it can continue execution. However, an optimization solution exists.

We can take advantage of the C# .NET multi-threading capability to process the Subject’s Notify for loop. This allows the code process to continue functioning while observers are notified in real-time. This is similar to the way Windows and ASP .NET handles events and callbacks.

First, we need to modify our abstract class Subject by including the following “using” line:

using System.Threading;

We then modify the Notify() function to include multi-threading with a .NET worker thread. The complete modified abstract class Subject is listed below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
abstract class Subject
{
    private List<IObserver> observers = new List<IObserver>();
    private static object MyLock = new object();
    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }
    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }
    public void Notify()
    {
        ThreadPool.QueueUserWorkItem(new WaitCallback(NotifyThread), observers);
    }
    static void NotifyThread(object stateInfo)
    {
        lock (MyLock)
        {
            List<IObserver> observers = (List<IObserver>)stateInfo;
            foreach (IObserver o in observers)
            {
                o.Update();
            }
        }
    }
}

Our modification moves the body of the Notify function into a separate function called NotifyThread. Note that our NotifyThread function is a static member and includes an object parameter. This allows the thread to call the function with a passed-in parameter of our list of observers to notify. The .NET worker thread takes care of notifying each observer while our main program continued processing.

While .NET offers various methods of multi-threading capability, this example uses the ThreadPool object’s QueueUserWorkItem method. This allows us to create a thread based off of the .NET Framework thread pool.

Another important note is our use of the keyword “lock” to provide basic thread synchronization. If you neglect to include a lock within the multithreaded procedure, your results may not appear as expected.

Conclusion

The Observer design pattern in C# ASP .NET can be a useful utility for allowing multiple classes to act upon a change in a target class. The Observer pattern provides a form of custom event handling and also allows for watching a subject’s state for changes. By additionally providing loose coupling of classes, the Observer pattern can add extensibility to your software design.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share