Continuing our comparison of design patterns in Ruby and C#, we’re taking a look at the Observer pattern. With this pattern, we have a subject and a list of observers that are interested in knowing when changes occur on the subject. This happens in a push model, the subject maintains the list observers and notifies each when needed.
In our examples, we’re going to create a Vehicle object that contains a couple of key properties, Mileage and MileageAtLastOilChange. When a new Vehicle instance is created both properties start at zero. As the vehicle’s Drive() method is called and miles are added to it, it notifies any known observers. The MilesUntilNextOilChange property lets others know how long the vehicle can be driven until it should get an oil change. When the PerformOilChangeMethod() is called, the MileageAtLastOilChange property is set to the current mileage and registered observers are notified that changes occurred.
We are also going to create a Dashboard object that will let us know how many miles the vehicle can be driven until it needs it’s next oil change as well as a warning message alerting the driver that it needs a oil change when it reaches 3000 miles.
C# Example
First we’ll create our Vehicle object as well as an IVehicleObserver interface that must be implemented by any Vehicle observers. Note that the Vehicle object contains an AddObserver() that allows observers to register themselves with the Vehicle.
using System.Collections.Generic; namespace DesignPatterns.ObserverPattern { public class Vehicle { private IList<IVehicleObserver> _observers = new List<IVehicleObserver>(); public int Mileage { get; private set; } public int MileageAtLastOilChange { get; private set; } public int MilesUntilNextOilChange { get { return 3000 - (Mileage - MileageAtLastOilChange); } } public Vehicle() { Mileage = 0; MileageAtLastOilChange = 0; } public void Drive(int miles) { Mileage += miles; NotifyObservers(); } public void PerformOilChange() { MileageAtLastOilChange = Mileage; NotifyObservers(); } public void AddObserver(IVehicleObserver observer) { _observers.Add(observer); } private void NotifyObservers() { foreach (var observer in _observers) observer.Notify(this); } } }
namespace DesignPatterns.ObserverPattern { public interface IVehicleObserver { void Notify(Vehicle vehicle); } }
And now we’ll create our Dashboard class which will implement the IVehicleObserver interface. When the Notify() method is called, it updates the Message property to reflect the vehicle’s current status.
namespace DesignPatterns.ObserverPattern { public class Dashboard : IVehicleObserver { public string Message { get; private set; } public Dashboard() { Message = string.Empty; } public void Notify(Vehicle vehicle) { Message = vehicle.MilesUntilNextOilChange <= 0 ? "Time for an oil change!" : string.Format("Next oil change is due in {0} miles.", vehicle.MilesUntilNextOilChange); } } } [/source] <p>Let’s take a look at how these two will work together.</p>
Ruby Example
The Ruby example really isn’t that much unlike the C# version. The big difference is that we don’t need to create an IVehicleObserver interface, we just need to make sure our Dashboard object has a “notify” method that accepts a Vehicle object.
class Vehicle attr_reader :mileage, :mileage_at_last_oil_change def initialize @mileage = 0 @mileage_at_last_oil_change = 0 @observers = [] end def drive(miles) @mileage += miles notify_observers end def miles_until_next_oil_change 3000 - (@mileage - @mileage_at_last_oil_change) end def perform_oil_change @mileage_at_last_oil_change = @mileage notify_observers end def add_observer(observer) @observers << observer end private def notify_observers @observers.each {|observer| observer.notify self} end end [/source] [/fusion_builder_column][fusion_builder_column row_column_index="5_6" type="1_1" background_position="left top" background_color="" border_size="" border_color="" border_style="solid" spacing="yes" background_image="" background_repeat="no-repeat" padding="" margin_top="0px" margin_bottom="0px" class="" id="" animation_type="" animation_speed="0.3" animation_direction="left" hide_on_mobile="no" center_content="no" min_height="none"][source language='ruby'] class Dashboard attr_reader :message def initialize @message = "" end def notify(vehicle) @message = vehicle.miles_until_next_oil_change <= 0 ? "Time for an oil change!" : "Next oil change is due in #{vehicle.miles_until_next_oil_change} miles." end end [/source] <p>And using the objects is very similiar to the C# example as well.</p>
Simplifying Our Objects
Both languages have built-in mechanisms that we can take advantage of that can make implementing the Observer pattern easy.
In .Net, we’re given out-of-the-box event handling. We just need to create a delegate that defines the method our observers will use for receiving notifications. This reduces the need for an IVehicleObserver interface and simplifies our design.
public delegate void MilesChangedHandler(Vehicle vehicle);
public class Vehicle { public int Mileage { get; private set; } public int MileageAtLastOilChange { get; private set; } public int MilesUntilNextOilChange { get { return 3000 - (Mileage - MileageAtLastOilChange); } } public event MilesChangedHandler MilesChangedEvent; public Vehicle() { Mileage = 0; MileageAtLastOilChange = 0; } public void Drive(int miles) { Mileage += miles; NotifyObservers(); } public void PerformOilChange() { MileageAtLastOilChange = Mileage; NotifyObservers(); } private void NotifyObservers() { if (MilesChangedEvent != null) MilesChangedEvent(this); } }
And no changes have to be made to our Dashboard object.
public class Dashboard : IVehicleObserver { public string Message { get; private set; } public Dashboard() { Message = string.Empty; } public void Notify(Vehicle vehicle) { Message = vehicle.MilesUntilNextOilChange <= 0 ? "Time for an oil change!" : string.Format("Next oil change is due in {0} miles.", vehicle.MilesUntilNextOilChange); } } [/source] <p>Using the classes changes a bit, note how we subscribe to event on the Vehicle object.</p>
Ruby also has some built goodness to make our work a little easier. We are actually given an Observable module that we can add to our Vehicle class that will handle the adding, removing, and notifying of observers. The only minor addition we need to add to our object is the a call to the “changed” method when we want to indicate that our object’s state has changed.
require "observer" class Vehicle include Observable attr_reader :mileage, :mileage_at_last_oil_change def initialize @mileage = 0 @mileage_at_last_oil_change = 0 @observers = [] end def drive(miles) @mileage += miles changed notify_observers self end def miles_until_next_oil_change 3000 - (@mileage - @mileage_at_last_oil_change) end def perform_oil_change @mileage_at_last_oil_change = @mileage changed notify_observers self end end
To use the module, our observers have to have an “update” method so we’ll need to rename our “notify” method on the Dashboard object.
class Dashboard attr_reader :message def initialize @message = "" end def update(vehicle) @message = vehicle.miles_until_next_oil_change <= 0 ? "Time for an oil change!" : "Next oil change is due in #{vehicle.miles_until_next_oil_change} miles." end end [/source] <p>And our usage stays exactly the same!</p>
That’s it for the Observer pattern, next time we’ll take a look at the Composite pattern.
I think most C#ers would implement the observer pattern with events with either a custom delegate:
public delegate void NotifyDelegate(Vehicle vehicle);
public event NotifyDelegate ChangeEvent;
or with a typed EventArgs
public EventHandler ChangeEvent;
public class VehicleEventArgs : EventArgs
{
public VehicleEventArgs(Vehicle vehicle) { Vehicle = vehicle; }
public Vehicle{ get; private set;}
}
(accidental early submit)
You would then subscribe to this event as such:
vehicle.ChangeEvent += new EventHandler(MethodName)
or with an anonymous function:
vehicle.ChangeEvent += new EventHandler(objAsVehicle => objAsVehicle.changeOil() );
@Matt
Thanks for the feedback! If you check out the “Simplifying Our Objects” part of the post I show pretty much the same thing. We created a MilesChangedHandler delegate and a corresponding event in the Vehicle object.
And our code samples demonstrates the shorthand method for subscribing to the event:
vehicle.MilesChangedEvent += dashboard.Notify;
Thanks for reading!
Ahh, indeed you did! Sorry about that, I had finished the Ruby section, left the article since my local build finished, came back later thinking I had finished the article!
Very nice! I like how you show the initial pattern in C#, as it better approximates how it is done in Ruby, and then go on to show the simplified versions.
[…] Comparing Design Patterns in Ruby and C#: The Observer Pattern – John Miller continues a series of posts on Design Pattern comparisons in C# and Ruby with a look at the Observer Pattern, looking at the raw implementation in both languages, and also looking at where the languages make it easier to implement the pattern […]
I’m glad delegates got a mention. Also, there is a Action option as well — Action is built-in and acts like a function you can pass around. The observers would be a list: private IList<Action> _observers;
Since this is a list of functions, you can call them in the NotifyObservers() method.
[…] the last post of the series, we took a look at the Observer pattern. This time we’re going to explore the […]