Observer Pattern

As part of figuring out the Garden App controls needed to complete the UI for the application, I realized I needed a tab control. Since there is not one out of the box, I started investigating a way of doing it. Every single tutorial I found was freaking complicated. I was looking at the problem and thinking, this is just something that would work well with an Observer pattern, why are they making things so difficult? So as a primer for that, I decided to give a reminder to everyone what I mean by the Observer Pattern. Lets dig into the Observer Pattern.

Observer Pattern

This pattern is pretty simple. You have two classes of things. The first is a subject and the second is an observer. The subject does something and notifies the observer of that thing. Then the observer then does something. You can have multiple observers for every subject. It’s just that simple.

As far as most implementations go, there is an interface created for each piece that have core functions used in the pattern.

Setup has a Register, Remove and Notify functions declared. The Observer interface will have an Update function.

Lets put a sample together.

Sample

In this example, we have news stories as the item that needs to be reported. The subject is a news hub of some sort that will take the stories and dish them out. Kind of like the AP. The observers are the various entities that can handle the news and distribute it. Think of this as your Radio Stations, Newspapers and TV Stations.

So get a C# WinForms application spun up and we will do some quick coding. I stored my Interfaces in a Interfaces folder, Observers in a Observer folder and my Subjects in a Subject folder.

First we need to set our Observer Pattern Interfaces.

Lets setup ISubject.

ISubject and IObserver

using System;
 
namespace ObserverPattern
{
    public interface ISubject
    {
        void Register(IObserver newsConsumer);
        void Remove(IObserver newsConsumer);
        void Notify(News newsItem);
    }
}

This is pretty easy. Like I said above we have the Register, Remove and Notify functions. I use the IObserver interface as the parameter type and a concrete News class as inputs to the functions. If there is a reason to decouple your product then feel free to do it as an interface but I wanted to keep it as simple as possible.

You have unhappy lines under your IObserver and News so lets knock the next one out with the IObserver interface.

using System;
using System.Collections.Generic;
using System.Text;
 
namespace ObserverPattern
{
    public interface IObserver
    {
        void Update(News newsItem);
 
 
        //Domain specific, this is not part of pattern
        int getNewsArticleCount();
    }
}

In this interface, we establish the Update signature for all the Observers. Since I am trying to keep this simple, I also included a getNewsArticleCount that has nothing to do with the pattern but is something all my observer functions will need. This is entirely to keep this as flat and simple as possible. Ignore it in your own implementations. You are only concerned with the Update signature.

One unhappy line down, one more to go so lets dig into the News class.

using System;
using System.Collections.Generic;
using System.Text;
 
namespace ObserverPattern
{
    public class News
    {
        public string Title;
        public string Body;
    }
}

Yep, it does nothing but corral all those little bits of data together that make up a story in our little, fictional world.

With all the bits and pieces sorted lets implement some interfaces. We will start with the simplest, the observers.

All the observers do is implement the pattern. This is a bit repetitive but I wanted to keep it as simple to follow as I could and adding extra layers of inheritance would not make life happy but would be something I would look into to clean this code up. With all that said lets, look at the Newspaper class.

News

using System;
using System.Collections.Generic;
using System.Text;
 
namespace ObserverPattern
{
    class Newspaper : IObserver
    {
        List<News> newsArticles = new List<News>();
        public void Update(News newsItem)
        {
            newsArticles.Add(newsItem);
        }
 
        //Does stuff after this
        public int getNewsArticleCount()
        {
            return newsArticles.Count;
        }
    }
}

First we establish that a newspaper has multiple news articles with the list collection of News objects. Then we implement the interface. Since this is a news feeder domain, the update is new news. If it was something else you would have different results.

Finally there is the comment that says the rest is just the application logic for whatever your domain would require. In our case it is the getNewsArticleCount. I stuck this in the interface because the GUI will just reference the interface. If you setup an object to inherit then you could swap out all this into it and just define it once. Keeping the interface as just the pattern and referencing the new parent object as the polymorphic reference. I did not do it here so we get to repeat this same code for both Radio and TV Stations. Just for easy I will include them here.

IObserver Implemented

using System;
using System.Collections.Generic;
using System.Text;
 
namespace ObserverPattern
{
    class RadioStation : IObserver
    {
        List<News> newsArticles = new List<News>();
        public void Update(News newsItem)
        {
            newsArticles.Add(newsItem);
        }
 
        //Does stuff after this
        public int getNewsArticleCount()
        {
            return newsArticles.Count;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
 
namespace ObserverPattern
{
    class TVStation : IObserver
    {
        List<News> newsArticles = new List<News>();
        public void Update(News newsItem)
        {
            newsArticles.Add(newsItem);
        }
 
        //Does stuff after this
        public int getNewsArticleCount()
        {
            return newsArticles.Count;
        }
    }
}

With all the observer patterns out of the way, lets look into the NewsManager or the ISubject implementation.

NewsManager

using System;
using System.Collections.Generic;
using System.Text;
 
namespace ObserverPattern
{
    class NewsManager : ISubject
    {
        List<IObserver> newsConsumers = new List<IObserver>();
 
        public void AddNews(News newsItem)
        {
            Notify(newsItem);
        }
 
        public List<IObserverGetConsumerCount()
        {
            return newsConsumers;
 
        }
 
        //Pattern Starts Here
        public void Notify(News newsItem)
        {
            newsConsumers.ForEach(x => x.Update(newsItem));
        }
 
        public void Register(IObserver newsConsumer)
        {
            newsConsumers.Add(newsConsumer);
        }
 
        public void Remove(IObserver newsConsumer)
        {
            newsConsumers.Remove(newsConsumer);
        }
    }
}

While this is slightly more complicated than the observer, it is pretty easy. First we establish out list of subscribed observers as a List of IObservers.

Next the AddNews and GetConsumerCount are domain specific code and not part of the pattern. You will note however that AddNews does call into the pattern with the call to Notify.

We have a comment to show where the interface pattern starts and we call all the subscribed observers’ update function when Notify is called. We add and subtract observers to the list in Register and Remove.

I will warn you. Because we are dealing with references here, the way this is organized might cause a memory leak. If you null out a reference to an object in the calling object before calling Remove, you will not release the memory until the calling object is garbage collected. I will show this in the Nuke button when we get to the GUI.

Speaking of the GUI.

GUI of Observer Pattern Demo

Visual Studio 2019 GUI View

This is a simple app. We have add and remove sections that will add and remove a specific observer. We have the big Add news button that will add news to all subscribed observers and display the results in the listbox to the right. Finally the Nuke button that will null out one our reference types so that when attempted to be added everything crashes.

Lets look at the code behind.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
 
namespace ObserverPattern
{
    public partial class Form1 : Form
    {
        NewsManager newsManager;
        Newspaper newspaper = new Newspaper();
        RadioStation radio = new RadioStation();
        TVStation tv = new TVStation();
 
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object senderEventArgs e)
        {
            newsManager = new NewsManager();
        }
 
        private void btnAddNewspaper_Click(object senderEventArgs e)
        {
            newsManager.Register(newspaper);
            btnAddNewspaper.Enabled = false;
            btnRemoveNewspaper.Enabled = true;
        }
 
        private void btnAddRadioStation_Click(object senderEventArgs e)
        {
            newsManager.Register(radio);
            btnAddRadioStation.Enabled = false;
            btnRemoveRadioStation.Enabled = true;
        }
 
        private void btnAddTVStation_Click(object senderEventArgs e)
        {
            newsManager.Register(tv);
            btnAddTVStation.Enabled = false;
            btnRemoveTVStation.Enabled = true;
        }
 
        private void btnRemoveNewspaper_Click(object senderEventArgs e)
        {
            newsManager.Remove(newspaper);
            btnAddNewspaper.Enabled = true;
            btnRemoveNewspaper.Enabled = false;
        }
 
        private void btnRemoveRadioStation_Click(object senderEventArgs e)
        {
            newsManager.Remove(radio);
            btnAddRadioStation.Enabled = true;
            btnRemoveRadioStation.Enabled = false;
        }
 
        private void btnRemoveTVStation_Click(object senderEventArgs e)
        {
            newsManager.Remove(tv);
            btnAddTVStation.Enabled = true;
            btnRemoveTVStation.Enabled = false;
        }
 
        private void btnAddNews_Click(object senderEventArgs e)
        {
            Guid guid = new Guid();
 
            News news = new News();
            news.Title = "Test Title " + guid.ToString();
            news.Body = "Test Body" + guid.ToString();
 
            newsManager.AddNews(news);
 
            lbResults.Items.Clear();
 
            foreach (IObserver source in newsManager.GetConsumerCount())
            {
                lbResults.Items.Add($"{source.ToString()} {source.getNewsArticleCount().ToString()}");
            }
        }
 
        private void btnNuke_Click(object senderEventArgs e)
        {
            newspaper = null;
        }
    }
}

Wrapping up Observer Pattern Demo

This should be pretty easy to follow but I will note a couple of things. We have one of each type of observer. This is a single reference hence why it is global. You could rearrange things to have access to multiple newpapers, tv and radio stations through a list but that would have made the code slightly more difficult to follow so I did not do it.

All the add button click events call the subject’s, the news manager, registration function to sign up for the news service. The Remove, does the same thing only removes the reference from the list unsubscribing from the service.

the Add News click event sets up a fake news story calls the AddNews function of the newsManager which will call the Notify action that a new story has been added then spits out the news sources story counts. You can play with it seeing how the newspaper and leave and come back to the service and the stories will only add when it is subscribed.

Finally the Nuke button will break the program. If you subscribe the news paper then null it then it remains in the news manager. Should you try to remove it then you will run into an epic fail of the application as it doesn’t know what you mean. The object still exists but you can’t get to it anymore. This is the memory leak.

Should you fall into this trap, you can get around it using Weak References but that is another post.

Leave a Reply

Your email address will not be published.