© Vaskaran Sarcar 2020
V. SarcarDesign Patterns in C#https://doi.org/10.1007/978-1-4842-6062-3_14

14. Observer Pattern

Vaskaran Sarcar1 
(1)
Garia, Kolkata, West Bengal, India
 

This chapter covers the Observer pattern.

GoF Definition

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Concept

In this pattern, there are many observers (objects) that are observing a particular subject (also an object). Observers want to be notified when there is a change made inside the subject. So, they register for that subject. When they lose interest in the subject, they simply unregister from the subject. Sometimes this model is called a Publisher-Subscriber (Pub-Sub) model. The whole idea can be summarized as follows: using this pattern, an object (subject) can send notifications to multiple observers (a set of objects) at the same time. Observers can decide how to respond to the notification, and they can perform specific actions based upon the notification.

You can visualize the scenarios with the following diagrams.

In step 1, three observers are requesting to get notifications from a subject (see Figure 14-1).
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig1_HTML.jpg
Figure 14-1

Step 1

In step 2, the subject can grant the requests; in other words, a connection is established (see Figure 14-2).
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig2_HTML.jpg
Figure 14-2

Step 2

In step 3, the subject is sending notifications to registered users (see Figure 14-3).
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig3_HTML.jpg
Figure 14-3

Step 3

In step 4 (optional), observer2 does not want to get further notifications and requests to unregister himself (or the subject doesn’t want to keep observer2 in its notification list due to some specific reason, and he unregisters observer2). So, the connection between the subject and observer2 has been broken (see Figure 14-4).
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig4_HTML.jpg
Figure 14-4

Step 4

In step 5, from now on, only Observer1 and Observer3 are getting notifications from the subject (see Figure 14-5).
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig5_HTML.jpg
Figure 14-5

Step 5

Real-World Example

Think about a celebrity who has many followers on social media. Each of these followers wants to get all the latest updates from their favorite celebrity. So, they follow the celebrity until their interest wanes. When they lose interest, they simply do not follow that celebrity. Think of each of these fans or followers as an observer and the celebrity as the subject.

Computer-World Example

Let’s consider a simple UI-based example in computer science. This UI is connected to some database. A user can execute a query through the UI, and after searching the database, the results are returned. With this pattern, you segregate the UI from the database. If a change occurs in the database, the UI should be notified so that it can update its display accordingly.

To simplify this scenario, assume that you are the person responsible for maintaining a database in your organization. Whenever there is a change made to the database, you want to get a notification so that you can take action if necessary. In this context, you can note the following points.
  • You can see the presence of this pattern in any eventdriven software. Modern languages like C# have built-in support for handling these events following this pattern. These constructs make your life easier.

  • If you are familiar with the .NET Framework, you see that in C#, you have generic System.IObservable<T> and System.IObserver<T> interfaces, in which the generic type parameter provides notifications.

Implementation

For this example, I created four observers (Roy, Kevin, Bose, and Jacklin) and two subjects (Celebrity-1 and Celebrity-2). A subject (in our example, Celebrity) maintains a list of all of its registered users. The observers receive a notification when the flag value changes in a subject.

Initially, three observers (Roy, Kevin, and Bose) registered themselves to get notifications from Celebrity-1. So, in the initial phase, all of them received notifications. But then, Kevin lost his interest in Celebrity-1. When Celebrity-1 became aware of this, he removes Kevin from his observer list. At this time, only Roy and Bose were receiving notifications (when the flag value is 50). But Kevin changed his mind later and wanted to get notifications again, so Celebrity-1 registers him again. This is why when Celebrity-1 sets the flag value to 100, all three observers have received notifications from him.

Later, you saw a celebrity named Celebrity-2. Roy and Jacklin are registered in his observer list. So, when Celebrity-2 sets the flag value to 500, both Roy and Jacklin have received the notification.

Let’s look at the code. The following is the IObserver interface , which has an Update(...) method.
    interface IObserver
    {
        void Update(ICelebrity subject);
    }
Two concrete classes—ObserverType1 and ObserverType2—show you that you can have different types of observers. These classes implement the IObserver interface as follows.
    // ObserverType1
    class ObserverType1 : IObserver
    {
        string nameOfObserver;
        public ObserverType1(String name)
        {
            this.nameOfObserver = name;
        }
        public void Update(ICelebrity celeb)
        {
            Console.WriteLine($"{nameOfObserver} has received an alert from {celeb.Name}.Updated value is: {celeb.Flag}");
        }
    }
    // ObserverType2
    class ObserverType2 : IObserver
    {
        string nameOfObserver;
        public ObserverType2(String name)
        {
            this.nameOfObserver = name;
        }
        public void Update(ICelebrity celeb)
        {
            Console.WriteLine($"{nameOfObserver} notified.Inside {celeb.Name}, the updated value is: {celeb.Flag}");
        }
    }
The subject interface (ICelebrity) contains three methods called Register(...), Unregister(...), and NotifyRegisteredUsers(), which are easy to understand. These methods register an observer, unregister an observer, and notify all registered observers, respectively. The following is the ICelebrity interface .
    interface ICelebrity
    {
        // Name of Subject
        string Name { get; }
        int Flag { get; set; }
        // To register
        void Register(IObserver o);
        // To Unregister
        void Unregister(IObserver o);
        // To notify registered users
        void NotifyRegisteredUsers();
    }
The Celebrity concrete class implements the ICelebrity interface. One important point is that this concrete class maintains a list of registered users. You see the following line of code inside this class.
List<IObserver> observerList = new List<IObserver>();
Note

In some examples of this pattern, you may see a slight variation where an abstract class is used instead of an interface (ICelebrity), and the list (observerList) is maintained in the abstract class. Both variations are fine. You can implement your preferred approach.

I used a constructor inside the Celebrity class . The constructor is as follows.
        public Celebrity(string name)
        {
            this.name = name;
        }
I use this constructor for different celebrities. So, inside the client code, you see the following lines with comments.
Console.WriteLine("Working with first celebrity now.");
ICelebrity celebrity = new Celebrity("Celebrity-1");
// some other code
// Creating another celebrity

ICelebrity celebrity2 = new Celebrity("Celebrity-2");

Lastly, I used an expression-bodied property inside the Celebrity class. You can see it in this code segment.
        //public string Name
        //{
        //    get
        //    {
        //        return name;
        //    }
        //}
        // Or, simply use expression bodied
        // properties(C# v6.0 onwards)
        public string Name => name;
Note

If you have a version of C# prior to 6.0, then you can use the commented code block instead. The same comment applies to similar code in this book.

The remaining code is easy to understand. Follow the supportive comments if you want.

Class Diagram

Figure 14-6 shows the class diagram.
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig6_HTML.jpg
Figure 14-6

The class diagram

Solution Explorer View

Figure 14-7 shows the high-level structure of the program.
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig7_HTML.jpg
Figure 14-7

Solution Explorer view

Demonstration

Here’s the complete demonstration.
using System;
// We have used List<Observer> here
using System.Collections.Generic;
namespace ObserverPattern
{
    interface IObserver
    {
        void Update(ICelebrity subject);
    }
    class ObserverType1 : IObserver
    {
        string nameOfObserver;
        public ObserverType1(String name)
        {
            this.nameOfObserver = name;
        }
        public void Update(ICelebrity celeb)
        {
            Console.WriteLine($"{nameOfObserver} has received an alert from {celeb.Name}. Updated value is: {celeb.Flag}");
        }
    }
    class ObserverType2 : IObserver
    {
        string nameOfObserver;
        public ObserverType2(String name)
        {
            this.nameOfObserver = name;
        }
        public void Update(ICelebrity celeb)
        {
            Console.WriteLine($"{nameOfObserver} notified.Inside {celeb.Name}, the updated value is: {celeb.Flag}");
        }
    }
    interface ICelebrity
    {
        // Name of Subject
        string Name { get; }
        int Flag { get; set; }
        // To register
        void Register(IObserver o);
        // To Unregister
        void Unregister(IObserver o);
        // To notify registered users
        void NotifyRegisteredUsers();
    }
    class Celebrity : ICelebrity
    {
        List<IObserver> observerList = new List<IObserver>();
        private int flag;
        public int Flag
        {
            get
            {
                return flag;
            }
            set
            {
                flag = value;
                // Flag value changed. So notify observer(s).
                NotifyRegisteredUsers();
            }
        }
        private string name;
        public Celebrity(string name)
        {
            this.name = name;
        }
        //public string Name
        //{
        //    get
        //    {
        //        return name;
        //    }
        //}
        // Or, simply use expression bodied
        // properties(C#6.0 onwards)
        public string Name => name;
        // To register an observer.
        public void Register(IObserver anObserver)
        {
            observerList.Add(anObserver);
        }
        // To unregister an observer.
        public void Unregister(IObserver anObserver)
        {
            observerList.Remove(anObserver);
        }
        // Notify all registered observers.
        public void NotifyRegisteredUsers()
        {
            foreach (IObserver observer in observerList)
            {
                observer.Update(this);
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Observer Pattern Demonstration.*** ");
            // We have 4 observers - 2 of them are ObserverType1, 1 is of // ObserverType2
            IObserver myObserver1 = new ObserverType1("Roy");
            IObserver myObserver2 = new ObserverType1("Kevin");
            IObserver myObserver3 = new ObserverType2("Bose");
            IObserver myObserver4 = new ObserverType2("Jacklin");
            Console.WriteLine("Working with first celebrity now.");
            ICelebrity celebrity = new Celebrity("Celebrity-1");
            // Registering the observers - Roy, Kevin, Bose
            celebrity.Register(myObserver1);
            celebrity.Register(myObserver2);
            celebrity.Register(myObserver3);
            Console.WriteLine(" Celebrity-1 is setting Flag = 5.");
            celebrity.Flag = 5;
            /*
            Kevin doesn't want to get further notification.
            So, unregistering the observer(Kevin)).
            */
            Console.WriteLine(" Celebrity-1 is removing Kevin from the observer list now.");
            celebrity.Unregister(myObserver2);
            // No notification is sent to Kevin this time. He has // unregistered.
            Console.WriteLine(" Celebrity-1 is setting Flag = 50.");
            celebrity.Flag = 50;
            // Kevin is registering himself again
            celebrity.Register(myObserver2);
            Console.WriteLine(" Celebrity-1 is setting Flag = 100.");
            celebrity.Flag = 100;
            Console.WriteLine(" Working with another celebrity now.");
            // Creating another celebrity
            ICelebrity celebrity2 = new Celebrity("Celebrity-2");
            // Registering the observers-Roy and Jacklin
            celebrity2.Register(myObserver1);
            celebrity2.Register(myObserver4);
            Console.WriteLine(" --Celebrity-2 is setting Flag value as 500.--");
            celebrity2.Flag = 500;
            Console.ReadKey();
        }
    }
}

Output

Here’s the output.
***Observer Pattern Demonstration.***
Working with first celebrity now.
 Celebrity-1 is setting Flag = 5.
Roy has received an alert from Celebrity-1. Updated value is: 5
Kevin has received an alert from Celebrity-1. Updated value is: 5
Bose notified.Inside Celebrity-1, the updated value is: 5
Celebrity-1 is removing Kevin from the observer list now.
 Celebrity-1 is setting Flag = 50.
Roy has received an alert from Celebrity-1. Updated value is: 50
Bose notified.Inside Celebrity-1, the updated value is: 50
 Celebrity-1 is setting Flag = 100.
Roy has received an alert from Celebrity-1. Updated value is: 100
Bose notified.Inside Celebrity-1, the updated value is: 100
Kevin has received an alert from Celebrity-1. Updated value is: 100
 Working with another celebrity now.
 --Celebrity-2 is setting Flag value as 500.--
Roy has received an alert from Celebrity-2. Updated value is: 500
Jacklin notified.Inside Celebrity-2, the updated value is: 500

Q&A Session

14.1 If there is only one observer, then I do not need to set up the interface. Is this correct?

Yes. But if you want to follow the pure object-oriented programming guidelines, you may always prefer interfaces (or abstract classes) over a concrete class. Aside from this point, there are usually multiple observers, and you implement them following the contracts. That’s where you benefit from this kind of design.

14.2 Can you have different type of observers?

Yes. Think about this in a real-world scenario. When anyone is making a crucial change in the organization’s database, multiple groups of people from different departments may want to know about the change (such as your boss and the owner of the database, who work at different levels) and act accordingly. So, you may need to provide support for different type of observers in your application. This is why in this chapter, I showed you an example involving multiple observers with multiple celebrities.

14.3 Can you add or remove observers at runtime?

Yes. Notice that at the beginning of the program, to get notifications, Kevin registers himself. Later, he unregisters and then reregisters.

14.4 It appears to me that there are similarities between the Observer pattern and the Chain of Responsibility pattern (see Chapter 22). Is this correct?

In an Observer pattern, all registered users receive notifications at the same time; but in the Chain of Responsibility pattern, objects in the chain are notified one by one, which happens until an object handles the notification fully (or, you reach at the end of the chain). Figure 14-8 and Figure 14-9 summarize the differences.
../images/463942_2_En_14_Chapter/463942_2_En_14_Fig8_HTML.jpg
Figure 14-8

Observer pattern

../images/463942_2_En_14_Chapter/463942_2_En_14_Fig9_HTML.jpg
Figure 14-9

Chain of Responsibility pattern

In Figure 14-9, I assume that Observer3 was able to process the notification completely. So, it is the end node of the chain. In this case, you also need to remember that you may need to take special action if the notification reaches at the end of the chain,but no one handles it properly.

14.5 Does this model support one-to-many relationships ?

Yes, the GoF definition confirms this. Since a subject can send notifications to multiple observers, this kind of dependency is depicting a one-to-many relationship.

14.6 There are ready-made constructs available (for example, System.IObservable<T>). Instead of using them, why are you writing your own code?

You cannot change ready-made functionalities, but I believe that when you try to implement the concept yourself, you better understand the ready-made constructs.

Another important point to note is that when you use the System.​IObservable<T> and System.​IObserver<T> interfaces, you need to be familiar with generic programming. Not only that, if you look closely at these interfaces, you see the following.

public interface IObservable<out T>

public interface IObserver<in T>

This simply means that you need to be familiar with covariance and contravariance in C# too. At first, these concepts may seem difficult. In my book Getting Started with Advanced C# (Apress, 2020), I discuss these concepts in detail with code examples.

14.7 What are the key benefits of the Observer pattern?

Here are some key advantages.
  • Subjects (celebrities in our example) and their registered users (observers) make up a loosely coupled system. They do not need to know each other explicitly.

  • You do not need to make changes to the subject when you add or remove an observer from its notification lists.

  • Also, you can add or remove observers at runtime independently.

14.8 What are the key challenges associated with an Observer pattern?

Here are some key challenges when you implement (or use) this pattern.
  • Undoubtedly, a memory leak is the greatest concern when you deal with events in C# (also known as a lapsed listener problem). An automatic garbage collector may not always help you in this context.

  • The order of notification is not dependable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.137.17