Background
In my family, my wife and I both need to work everyday, therefore we drop our kids to the daycare center from 8:00 am to 5:00 pm.
We usually call the daycare a couple times a day to know about our kids, more about their daily activities. If the kids are sick, we contact the kid's doctor to have his medical advice.
Sometimes we found it's not very convenient to make so many calls during work time. However, I'll really appreciate if the daycare could have an automatic system to keep us notified about our kids' daily status. If the kid doesn't feel well, the doctor should start receiving the kid's daily reports as well.
Introduction
The Observer Pattern describes the dependence relationship between one object (observable) to many objects (observers). It's also called Model/View, Dependents, or Distributor/Listener pattern.
This article introduces an implementation when we can use the Observer pattern to keep all observer objects updated after things have changed.
In this case, the kid is an ObservableObject
that needs to send out the notification when the daycare updates her daily status. The daycare will determine who will be notified when the daily status is updated.
A kid can be watched by multiple observers, and also one observer can receive multiple updated daily status reports.
Observer Design Pattern Structure
Class Diagram
I am a big fan of Delegates/Events, so I decided to create an Event object to handle all the observer calls through multiple delegation in my ObservableObject
.
The ObservableObject
should be able to allow the client to add/remove observer objects as well. An observer could also receive multiple ObservableObject
whenever status is being updated.
Implementation Code
ObservableObject Class
ObservableObject
is an abstract base class that implements the methods to interactive with Observers. It allows the client to add/remove an observer to receive the notifications.
The Notify()
method in ObservableObject
handles the child level events raised. AddObserver
and RemoveObserver
queue the observer's actions by using muticast delegation.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace www.askbargains.com.ObserverDesignPatternLib
{
public delegate void NotifyObserver(string key);
public abstract class ObservableObject
{
public event NotifyObserver NotifyObserverEvent;
public void AddObserver(NotifyObserver ob)
{
NotifyObserverEvent += ob;
}
public void RemoveObserver(NotifyObserver ob)
{
NotifyObserverEvent -= ob;
}
public void Notify(string kidName)
{
if (NotifyObserverEvent != null)
{
NotifyObserverEvent(kidName);
}
}
}
}
Kid Class
The Kid
class is a child class of our ObservableObject
. It also contains its own information, such as status. In the DailyStatus
property, we will raise Notify
event whenever DailyStatus
is being updated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace www.askbargains.com.ObserverDesignPatternLib
{
public class Kid : ObservableObject
{
public string Name { get; set; }
private Status _dailyStatus;
public Status DailyStatus
{
get { return _dailyStatus; }
set
{
_dailyStatus = value;
Notify(this.Name);
}
}
}
}
Parent Class and FamilyDoctor Class
We also create Parent
and FamilyDoctor
as our observers. DailyStatusUpdate()
in the Parent
class and ReciveNotes()
in the FamilyDoctor
class are both local update methods (take only one string parameter), which meet the declaration of the NotifyObserver
delegate type in our ObservableObject
. That's how we achieve our handshake process between Observer
and ObserverableObject
.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace www.askbargains.com.ObserverDesignPatternLib
{
public class Parent
{
private Dictionary<string, Kid> _kids = new Dictionary<string,>();
public Dictionary<string, Kid> Kids
{
get { return _kids; }
set { _kids = value; }
}
public void DailyStatusUpdate(string key)
{
Console.WriteLine("Parents received {0}'s daily status. " +
"updated on {1}, Notes: {2} ",
Kids[key].Name, Kids[key].DailyStatus.UpdatedOn,
Kids[key].DailyStatus.Description);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace www.askbargains.com.ObserverDesignPatternLib
{
public class FamilyDoctor
{
private Dictionary<string, Kid> _kids = new Dictionary<string,>();
public Dictionary<string, Kid> Patients
{
get { return _kids; }
set { _kids = value; }
}
public void ReciveNotes(string patientName)
{
Console.WriteLine("Family Doctor received {0}'s new daily status. " +
"updates on: {1} . Notes:{2}",
Paitients[patientName].Name,
Paitients[patientName].DailyStatus.UpdatedOn,
Paitients[patientName].DailyStatus.Description);
}
}
}
Client App
Once we have our Observer library ready, we can consume it from any application. I am using a console app to demo the client side code.
First, from my client app, a console app will act as the daycare teacher to update the daily status for my kids every 5 seconds.
Second, Aimee and Elizabeth's status will be updated from my side every 5 seconds as well. A short description should let me know what the current status is.
Third, Elizabeth has a family doctor; if she is sick, the daycare teacher has to have her doctor involved to monitor Elizabeth's status as well (she is still a tiny baby). Once she goes back to normal activity, the daycare teacher will remove the doctor from Elizabeth's observer list (so we don't bother the doctor when everything is fine).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using www.askbargains.com.ObserverDesignPatternLib;
namespace www.askbargains.com.ObserverDesignPatternClient
{
class Program
{
static void Main(string[] args)
{
Kid kid1 = new Kid();
Kid kid2 = new Kid();
kid1.Name = "Aimee";
kid2.Name = "Elizabeth";
Parent parent1 = new Parent();
parent1.Kids.Add(kid1.Name, kid1);
parent1.Kids.Add(kid2.Name, kid2);
FamilyDoctor doc1 = new FamilyDoctor();
doc1.Patients.Add(kid2.Name, kid2);
kid1.AddObserver(new NotifyObserver(parent1.DailyStatusUpdate));
kid2.AddObserver(new NotifyObserver(parent1.DailyStatusUpdate));
kid1.DailyStatus = new Status(String.Format("{0} is happy", kid1.Name));
kid2.DailyStatus = new Status(String.Format("{0} is fuzzy", kid2.Name));
Thread.Sleep(5000);
kid2.AddObserver(new NotifyObserver(doc1.ReciveNotes));
kid1.DailyStatus = new Status(String.Format("{0} is happy", kid1.Name));
kid2.DailyStatus = new Status(String.Format("{0} is sick. " +
"Tempture : 39.7", kid2.Name));
Thread.Sleep(5000);
kid1.DailyStatus = new Status(String.Format("{0} is happy", kid1.Name));
kid2.DailyStatus = new Status(String.Format("{0} is back to normal. " +
"she is happy now", kid2.Name));
Thread.Sleep(5000);
kid2.RemoveObserver(new NotifyObserver(doc1.ReciveNotes));
kid1.DailyStatus = new Status(String.Format("{0} is happy. " +
"Just had a big lunch", kid1.Name));
kid2.DailyStatus = new Status(String.Format("{0} is happy. " +
"Playing with her best friend Kevin", kid2.Name));
Console.WriteLine("Daily Report End!");
Console.Read();
}
}
}
Once we start our client app, you will see all the messages being printed about my two kids. Cool.
Conclusion
From this article, I demonstrated how we can use delegation/events to attach observers to an object. All the current messages can be received from the observers when it's attached.