The Observer Pattern

Let's start our exploration of events at the basis for their implementation: the Observer pattern. Observer is a design pattern. To borrow the definiton from Head-First Design Patterns, a design pattern is a solution to a problem in a specific context. Design patterns almost always result in more flexible code at the expense of increased complexity. Design patterns tend to be language-agnostic, so it's rare that learning to use one doesn't benefit you in all cases. Let's talk about Observer.

Patterns? Observer? What?

Observer is a design pattern that you use when you have a class that needs to notify other classes when something happens, but you either don't know or don't want to limit the classes that can receive the notification. The image below is a pseudo-UML diagram that describes the Observer pattern.

Observer pattern diagram

The class that wants to notify other classes is called the target. In order for it to post these notifications, we have to provide an interface that allows classes to indicate they are interested in the notifications; this is the purpose of the IObservable interface (I'll discuss the purpose of each method in a moment.) The classes that are interested in notifications are called subjects. In order for the target to notify the subjects, the subjects must have a method that the target can call. This is the purpose for the IObserver interface.

Before we get into the technical details, let's take a moment to discuss how these names help describe the situation in case it got lost in the paragraph above. The target wants to notify other types when something happens; to indicate that it wants to do this it implements IObservable. An observable object is one that can be observed. The objects that want to observe the target are called subjects, and in order to be able to observe the target they have to implement IObserver. So, observers exist to observe; observables exist to be observed. Make sense?

Now for the technical details. Notice that IObserver provides the Update() method. This is the method that the target must call whenever it needs to notify its subjects. When any subject wants to be notified, it calls the target's AddListener() method so that it will be added to the list of IObserver objects that will be notified. This is why we need IObserver: through interface implementation we can have our target notify classes of any type that implements the interface, even if the type is one we've never heard of. Notice the Notify() method provided by IObserver; when the target needs to notify its subjects, it will call this method. (Side note: technically, I would argue that Notify() should be a private or protected method because only the target should be able to notify subjects that something has happened. Unfortunately, you cannot define a private method with an interface. Just consider Notify()'s presence in the diagram as a kind of helper to remind you that it is important to the pattern.) A typical implementation of Notify() might look like this:

Private Sub Notify()
    For Each subject In _subjects
        subject.Update()
    Next
End Sub

Since all of the subjects implement IObserver, they all have an Update() method. The subject responds to Update() however it wishes. The important thing to take away here is the Observer pattern enables you to send a notification to a wide variety of classes that you don't know about at design time.

The Example

You'll find the example in the WeatherStationWithObserver project in the example solution. Since it's the first time you've seen the application, let's spend some time talking about what it does before I go over how it implements the solution using the Observer pattern.

You've been tasked with writing a weather station application (side note: I lifted the spirit of this example from the book Head-First Design Patterns; it's not identical but I felt like being honest.) The weather station consists of two parts: a base station that displays information to the user and modular sensors that plug in to the base station. Users purchase the base station and any number of sensors. Sensors can be made by third parties, but they will be guaranteed to implement any interface you deem mandatory. Sensors will update at intervals determined by the manufacturer, and your base station needs to update immediately when a sensor provides new data. It just so happens the company has a temperature sensor they want you to implement the code for so you can decide on an interface that sensors should implement.

This is perfect for the Observer pattern. Your base station should be a subject, and the sensors should be the targets. The base station will subscribe to any sensors that are connected, and when a sensor updates it will update its display. For the purposes of simplicity, I am going to implement the example as if only the temperature sensor is supported, then discuss how it could have been changed to support any type of sensor (which will give you an idea of why it would have made the example less clear.)

I'm going to assume you get IObserver and IObservable and skip on to ISensor, since the first two are on the diagram and are exactly as indicated. ISensoris the interface that we will require out of every sensor that wants to be compatible with the base station. We will require that the sensors implement IObservable and provide a Name and their Data as a Double. Since every sensor will implement this interface, the base station has a fighting chance of displaying information about the sensor.

Let's look at TemperatureSensor next. It implements ISensor to respect the contract the base station requires. AddListener() and RemoveListener() simply add or remove the IObserver to the private list of observers. The temperature sensor itself starts a timer that ticks every second. When the timer ticks, the sensor makes a new reading and notifies any observers that a new temperature reading is available by calling Notify(). Since this is just an example, all it does is generate a random temperature between 70 and 90 degrees (Fahrenheit).

Now, let's look at MainForm, which implements the base station since I'm not trying to overwhelm you with a full-fledged presentation model that avoids implementing logic in its UI. There's two constructors in MainForm, because I wanted to demonstrate a feature. The constructor that takes a TemperatureSensor parameter subscribes to the sensor's updates. The parameterless constructor creates a new TemperatureSensor and passes this to the other constructor. When the sensor calls the form's Update() method, the form gets the latest temperature reading and displays it. Note that the sensor's Name property is used to fill in the label above the temperature reading.

Now for the final feature, and the reason for the extra constructor. When you click on the form (which is kind of hard; I put the ugly border around the temperature to give you a fighting chance) another form is displayed, and it uses the same sensor as the first form.

Optional Side Notes

Something I decided to omit from the example because it would have hidden the concepts beneath some needless complexity is the ability to support multiple sensors. You can do this yourself if you feel like testing your knowledge. The first hurdle is the form itself needs a collection to store more than one sensor, and likely a pair of add/remove methods to implement connecting and disconnecting sensors. The elaborate part of the project involves displaying the data from the sensors. I'd do this by creating a user control based on the two labels and using a hashtable to link controls to their sensors. Obviously, this might have left you scratching your head over implementation details that have nothing to do with the subject of this demo.

Final Notes

We had to do a good bit of work to implement the Observer pattern, but the end result is we have a base weather station that can support sensors we don't know about so long as the sensors implement the ISensor interface. The sensors can update whenever they want, so long as they notify our base station that their data has updated.

In the next demo, we'll discuss how we can make the implementation of our weather station a little more simple if we modify our interpretation of the Observer pattern to use delegates, a powerful .NET feature that's going to remove some of the burden from our backs.