Comparing Design Patterns in Ruby and C#: The Observer Pattern

Home/.Net, Ruby/Comparing Design Patterns in Ruby and C#: The Observer Pattern

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);
        }
    }
}
&#91;/source&#93;

<p>Let’s take a look at how these two will work together.</p>

var dashboard = new Dashboard(); var vehicle = new Vehicle(); vehicle.AddObserver(dashboard); vehicle.Drive(2000); Console.WriteLine(dashboard.Message); vehicle.Drive(1500); Console.WriteLine(dashboard.Message); vehicle.PerformOilChange(); Console.WriteLine(dashboard.Message); vehicle.Drive(1000); Console.WriteLine(dashboard.Message);

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
&#91;/source&#93;
&#91;/fusion_builder_column&#93;&#91;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"&#93;&#91;source language='ruby'&#93; 
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
&#91;/source&#93;
<p>And using the objects is very similiar to the C# example as well.</p>
require "dashboard" require "vehicle" dashboard = Dashboard.new vehicle = Vehicle.new vehicle.add_observer dashboard vehicle.drive 2000 puts dashboard.message vehicle.drive 1500 puts dashboard.message vehicle.perform_oil_change puts dashboard.message vehicle.drive 1000 puts dashboard.message

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);
    }
}
&#91;/source&#93;
<p>Using the classes changes a bit, note how we subscribe to event on the Vehicle object.</p>
var dashboard = new Dashboard(); var vehicle = new Vehicle(); vehicle.MilesChangedEvent += dashboard.Notify; vehicle.Drive(2000); Console.WriteLine(dashboard.Message); vehicle.Drive(1500); Console.WriteLine(dashboard.Message); vehicle.PerformOilChange(); Console.WriteLine(dashboard.Message); vehicle.Drive(1000); Console.WriteLine(dashboard.Message);

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
&#91;/source&#93;
<p>And our usage stays exactly the same!</p>
require "dashboard" require "vehicle" dashboard = Dashboard.new vehicle = Vehicle.new vehicle.add_observer dashboard vehicle.drive 2000 puts dashboard.message vehicle.drive 1500 puts dashboard.message vehicle.perform_oil_change puts dashboard.message vehicle.drive 1000 puts dashboard.message

That’s it for the Observer pattern, next time we’ll take a look at the Composite pattern.

kick it on DotNetKicks.com

About the Author:

Freelance software developer in the Cleveland area.

7 Comments

  1. Matt July 20, 2009 at 8:18 am - Reply

    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;}
    }

  2. Matt July 20, 2009 at 8:20 am - Reply

    (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() );

  3. John Miller July 20, 2009 at 8:33 am - Reply

    @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!

  4. Matt July 20, 2009 at 10:34 am - Reply

    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.

  5. […] 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 […]

  6. Clay Lenhart July 21, 2009 at 6:40 am - Reply

    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.

  7. […] the last post of the series, we took a look at the Observer pattern. This time we’re going to explore the […]

Leave A Comment