Introduction
The Observer Pattern is arguably the one of the most interesting, if not the most important, design pattern in the GOF Design
Pattern book1.
The intent of the pattern is to define a one-to-many
dependency between objects so that when one object changes state, all its dependents are notified and updated automatically
2.
The objective of this article is to map this definition to the C#.net world, i.e. provide an implementation that is as simple as possible
and that takes advantage of the framework.
I've read articles that touched on the subject. But I feel that they either fall short of mapping everything (see participants below); they try to re-implement without using available C# constructs, and/or fail to cite references.
This is also a note-to-self. Hopefully it will refine my understanding as I write it; and expand it from your comments.
1[Gamma1995]
2[Gamma1995], p.326
Background
from the GOF Design Pattern book3
Subject
- knows its observers. Any number of Observer objects may observe a subject.
- provides an interface for attaching and detaching Observer objects.
Observer
- defines an updating interface for objects that should be notified of changes in a subject.
ConcreteSubject
- stores state of interest to ConcreteObserver objects.
- sends a notification to its observers when its state changes.
ConcreteObserver
- maintains a reference to a ConcreteSubject object.
- stores state that should stay consistent with the subject's.
- implements the Observer updating interface to keep its state consistent with the subject's.
I've quoted this from the book because I felt it concisely defines what the pattern is and what the participants are.
Note that the book also tackles how to implement the pattern4 and gives sample codes in c++5 . I suggest reading the book because the ideas are quite useful and timeless.
3[Gamma1995],Structure and Participants, p.328
4[Gamma1995], p.330
5[Gamma1995], p.334
Delegates and Events
The easiest way to implement the observer pattern in .Net is using delegates and events.
Microsoft calls the class that raises events a publisher and the classes that handle events subscribers6. Notably, it is also known as "Publish-Subscribe" in the GOF book7.
I will try to define the components in the sequence how I normally write them in code. I find this useful when I'm reusing the pattern. Pardon my extensive use of bullets but I find them useful when making a point.
6[Events]
7[Gamma1995], p.326
Delegate
8[Delegates]
9[Interface]
10[Guidelines]
11[Guidelines]
Event
- The event is a keyword that is used as a modifier for delegates to declare an "event" in a class.12 An "event" is something of interest, e.g. a state changed or a method was invoked.
- An event is a multicast delegate. It allows multiple methods with matching signatures; this doesn't need to be exact13, to subscribe.
- The following code shows how an event based on the System
EventHandler
is declared.
public class AClass
{
public event EventHandler MyEvent;
}
- The compiler automatically adds event accessors, add and remove.14 This makes the code above equivalent, sans locking, to the following code:
private EventHandler myEvent;
public event EventHandler MyEvent
{
add
{
click = (EventHandler)Delegate.Combine(click, value);
}
remove
{
click = (EventHandler)Delegate.Remove(click, value);
}
}
- This shows how Event Handlers, i.e. Observers, are Attached and Detached.
- To notify the Observers you just call the event. But the following is a more robust implementation that follows MS' guideline:
protected virtual void OnAPropertyChanged(EventArgs e)
{
EventHandler handler = MyEvent;
if (handler != null)
{
handler(this, e);
}
}
- Note that the event is initially null so you need to check if there are subscribers before invoking. And we invoke the copy to make sure it doesn't become null before we use it, i.e. to make it thread safe15.
- An event maps to the Subject. It knows its Observers, i.e. subscribers. It allows any number of Observers, i.e. it's a multicast delegate. It provides an interface for attaching and detaching, i.e. add and remove.
12[event]
13[Variance]
14[Custom]
15[Thread]
ConcreteSubject
- The ConcreteSubject stores the state of interest and sends notifications when the state changes. This map nicely to any class that has a state, i.e. a field, and declares an event for that field, or its accessor, the property.
- The only caveat is GOF's diagram16 has the ConcreteSubject inheriting from the Subject. We can either wrap the event in an inheritable type, e.g. an interface or just let it be.
- A sample of wrapping an event is the
System
ComponentModel
IComponent
:
namespace System.ComponentModel
{
public interface IComponent : IDisposable
{
...
event EventHandler Disposed;
}
}
- One thing I find odd about MS' implementation is the state, i.e.
IsDisposed
, is defined in System.Windows.Forms.Control
. I guess it's also odd that the event is under IComponent
and not IDisposable
. I agree that mixing in composition is better than just inheritance17 but wouldn't it be more readable and reusable to have the event, property et al. related to IDisposable
be under it? - I digress. The point is you can wrap your event in an interface or a base class so you can do type inheritance. But I think this is unnecessary. My take is composition, while not strictly type inheritance, is just as good if not better. As I mentioned earlier, delegates act like interfaces, similar to prototype-based inheritance models18. So just by declaring events you are already inheriting all of the goodness of delegates. In our sample before
AClass
is our ConcreteSubject. The only thing missing is the state of interest which we will implement using a field and a property. We will also hook-up the property setter to the event so we will have automatic notifications. Be sure to check the setter value if it is actually changing the state. This will save you a lot of unnecessary notifications.
public class AClass
{
private int stateOfInterest;
public event EventHandler StateOfInterestChanged;
public int StateOfInterest
{
get
{
return this.stateOfInterest;
}
set
{
if (this.stateOfInterest != value)
{
this.stateOfInterest = value;
this.OnStateOfInterestChanged(EventArgs.Empty);
}
}
}
protected virtual void OnStateOfInterestChanged(EventArgs e)
{
EventHandler handler = this.StateOfInterestChanged;
if (handler != null)
{
handler(this, e);
}
}
}
16[OMT]
17[Composition]
18[Differential]
ConcreteObserver
- That leaves us with the ConcreteObserver. In the .Net world it is simply the class that subscribes to the event. There are many ways to subscribe to events19 but try to use the programmatic approach so you have more control.
public class BClass
{
AClass aclass;
public BClass()
{
this.aclass = new AClass();
this.aclass.StateOfInterestChanged +=
this.AClass_StateOfInterestChanged;
}
void AClass_StateOfInterestChanged(object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
- Remember that when properly implemented you can cast the sender to an
AClass
. So even if you have more than one AClass
, e.g. a collection of AClass
, you can use the same handler.
public class CClass
{
private ObservableCollection<aclass> listOfAClass;
public CClass()
{
this.listOfAClass = new ObservableCollection<aclass>();
this.listOfAClass.CollectionChanged +=
new NotifyCollectionChangedEventHandler(
this.ListOfAClass_CollectionChanged);
}
private void ListOfAClass_CollectionChanged(
object sender,
NotifyCollectionChangedEventArgs e)
{
e.OldItems.Cast<aclass>()
.ToList<aclass>()
.ForEach(
a => a.StateOfInterestChanged -=
this.AClass_StateOfInterestChanged);
e.NewItems.Cast<aclass>()
.ToList<aclass>()
.ForEach(
a => a.StateOfInterestChanged +=
this.AClass_StateOfInterestChanged);
}
private void AClass_StateOfInterestChanged(
object sender, EventArgs e)
{
throw new NotImplementedException();
}
}
19[Subscribe]
Summary
My object was to map the observer pattern to the .Net world using simple implementations. We achieved this by simply using delegates and events.
We added a few tweaks here and there, e.g. check if the property value is actually changing before triggering the event, or have an OnXXXChanged()
method as described in the MS guideline. But overall implementing it is straight forward.
A bigger question I guess "how do I apply it?". Controls are great samples of the pattern; whether win form or web. For simple cases you can depict controls as the subject and the form or container as the observer, e.g. CheckBox
has a CheckState
and a CheckedStateChanged
event, so one can easily see how this maps to the ConcreteSubject, State of Interest, and Subject.
But controls are more powerful when implemented as Observers using complex binding; and that, I think, is a subject for a different article.
History
- 20120911 - Original Version
References
[Gamma1995]; Gamma, E., R. Helm, R. Johnson, and J. Vlissides; 1995; Design Patterns: Elements of Reusable Object-Oriented Software; Addison Wesley Professional.
[Delegates]; Delegates (C# Programming Guide); http://msdn.microsoft.com/en-us/library/ms173171(v=vs.100).aspx
[Events]; Events (C# Programming Guide); http://msdn.microsoft.com/en-us/library/awbftdfh(v=vs.100).aspx
[Guidelines]; How to: Publish Events that Conform to .NET Framework Guidelines (C# Programming Guide); http://msdn.microsoft.com/en-us/library/w369ty8x(v=vs.100).aspx
[event]; event (C# Reference); http://msdn.microsoft.com/en-us/library/8627sbea(v=vs.100)
[Custom]; How to: Implement Custom Event Accessors (C# Programming Guide); http://msdn.microsoft.com/en-us/library/bb882534(v=vs.100).aspx
[Interface]; Interface (computing); http://en.wikipedia.org/wiki/Interface_(computing)
[Variance]; Using Variance in Delegates (C# and Visual Basic) http://msdn.microsoft.com/en-us/library/ms173174(v=vs.100).aspx
[Thread]; Thread safety; http://en.wikipedia.org/wiki/Thread_safety
[OMT]; Object-modeling technique; http://en.wikipedia.org/wiki/Object-modeling_technique
[Composition]; Composition over inheritance; http://en.wikipedia.org/wiki/Composition_over_inheritance
[Differential]; Differential inheritance; http://en.wikipedia.org/wiki/Differential_inheritance
[Subscribe] How to: Subscribe to and Unsubscribe from Events (C# Programming Guide); http://msdn.microsoft.com/en-us/library/ms366768(v=vs.100).aspx