Most of my career has been in .Net development and I’m pretty comfortable applying design patterns in C#, but as I’m learning Ruby, I was finding it difficult to figure out how to implement them without creating awkward, hard-to-read code. Recently a local Ruby guru, Nate Klaiber, recommended that I pick up the book Design Patterns in Ruby. Well, he more than recommended it, he actually gave a copy autographed by the author (totally unexpected but very, very appreciated!). He has a thorough review of the book (and many others) on his book review site.

As I read through the book, I’m going to do a series of posts showing examples of how each pattern would be applied in both C# and Ruby. (The book has around a dozen patterns so this will probably take several weeks to get through them all).

The first one we’re going to work through is the Template pattern, which is arguably one of the easiest patterns to learn. The template pattern involves creating a base class with methods that can be overridden by subclasses. The base class itself has a central method that will call the other methods in the object.

In the examples below, we’re going to create two vehicle factory classes, one that creates a vehicle specifically for winter travelling, and another that creates vehicles for summertime cruising.

*Note: I’m certainly no Ruby expert (or C# expert for that matter), but am learning here as I go. If you have a better way to do something, please leave a comment!

C# Example

The template method is all over the .Net framework. A perfect example is ASP.Net’s page lifecycle. You’re given hooks to perform actions during page load, init, etc.

Let’s start by creating our Vehicle class.

using System.Collections.Generic;

namespace DesignPatterns.TemplatePattern
{
    public class Vehicle
    {
        public string SteeringWheel { get; set; }
        public string Tires { get; set; }
        public string Seats { get; set; }
        public string Engine { get; set; }
        public string MakeModel { get; set; }
        public IList<string> Amenities { get; set; }

        public Vehicle()
        {
            Amenities = new List<string>();
        }
    }
}

Next we’ll create our base class. Note that CreateVehicle() is the only public method. The rest are only accessible to objects that derive from this class. And some of the “hook” methods have a default implementation while others are declared abstract and must be overridden by the subclass.

namespace DesignPatterns.TemplatePattern
{
    public abstract class VehicleFactoryBase
    {
        protected Vehicle VehicleUnderConstruction { get; set; }

        protected virtual void AddSteeringWheel()
        {
            VehicleUnderConstruction.SteeringWheel = "Standard Steering Wheel";
        }

        protected virtual void AddTires()
        {
            VehicleUnderConstruction.Tires = "All Season Tires";
        }

        protected virtual void AddSeats()
        {
            VehicleUnderConstruction.Seats = "Cloth Bucket Seats";
        }

        protected virtual void AddAmenities()
        {
            
        }

        protected abstract void AddEngine();

        protected abstract void AddMakeModel();

        public Vehicle CreateVehicle()
        {
            VehicleUnderConstruction = new Vehicle();
            AddSteeringWheel();
            AddTires();
            AddSeats();
            AddAmenities();
            AddEngine();
            AddMakeModel();
            return VehicleUnderConstruction;
        }
    }
}

Now we’re ready to create our first implementation, a vehicle factory that creates vehicles designed for living in the snow belt.

namespace DesignPatterns.TemplatePattern
{
    public class WinterVehicleFactory : VehicleFactoryBase
    {
        protected override void AddTires()
        {
            VehicleUnderConstruction.Tires = "Snow Tires";
        }

        protected override void AddAmenities()
        {
            VehicleUnderConstruction.Amenities.Add("4 Wheel Drive");
            VehicleUnderConstruction.Amenities.Add("Snow Plow");
        }

        protected override void AddEngine()
        {
            VehicleUnderConstruction.Engine = "4.7L V-8";
        }

        protected override void AddMakeModel()
        {
            VehicleUnderConstruction.MakeModel = "Jeep Grand Cherokee";
        }
    }
}

Our second implementation will be a vehicle factory that creates our summer cruiser.

namespace DesignPatterns.TemplatePattern
{
    public class SummerVehicleFactory : VehicleFactoryBase
    {
        protected override void AddSeats()
        {
            VehicleUnderConstruction.Seats = "Leather Bucket Seats";
        }

        protected override void AddAmenities()
        {
            VehicleUnderConstruction.Amenities.Add("Premium Sound System");
        }

        protected override void AddEngine()
        {
            VehicleUnderConstruction.Engine = "3.7L V6";
        }

        protected override void AddMakeModel()
        {
            VehicleUnderConstruction.MakeModel = "Nissan 370Z Coupe";
        }
    }
}

And finally, below are the unit tests needed to verify both factories creates their vehicles as expected.

using NUnit.Framework;

namespace DesignPatterns.TemplatePattern
{
    [TestFixture]
    public class When_creating_a_winter_vehicle
    {
        private Vehicle winterVehicle;

        [SetUp]
        public void EstablishContext()
        {
            winterVehicle = new WinterVehicleFactory().CreateVehicle();
        }

        [Test]
        public void Should_create_a_Jeep_Grand_Cherokee()
        {
            Assert.That(winterVehicle.MakeModel, Is.EqualTo("Jeep Grand Cherokee"));
        }

        [Test]
        public void Should_have_a_standard_steering_wheel()
        {
            Assert.That(winterVehicle.SteeringWheel, Is.EqualTo("Standard Steering Wheel"));
        }

        [Test]
        public void Should_have_snow_tires()
        {
            Assert.That(winterVehicle.Tires, Is.EqualTo("Snow Tires"));
        }

        [Test]
        public void Should_have_a_V8_engine()
        {
            Assert.That(winterVehicle.Engine, Is.EqualTo("4.7L V-8"));
        }

        [Test]
        public void Should_have_standard_cloth_seats()
        {
            Assert.That(winterVehicle.Seats, Is.EqualTo("Cloth Bucket Seats"));
        }

        [Test]
        public void Should_have_4_wheel_drive()
        {
            Assert.That(winterVehicle.Amenities.Contains("4 Wheel Drive"));
        }

        [Test]
        public void Should_have_a_snow_plow()
        {
            Assert.That(winterVehicle.Amenities.Contains("Snow Plow"));
        }

        [Test]
        public void Should_not_have_any_other_amenities()
        {
            Assert.That(winterVehicle.Amenities.Count, Is.EqualTo(2));
        }
    }
}
using NUnit.Framework;

namespace DesignPatterns.TemplatePattern
{
    [TestFixture]
    public class When_creating_a_summer_vehicle
    {
        private Vehicle summerVehicle;

        [SetUp]
        public void EstablishContext()
        {
            summerVehicle = new SummerVehicleFactory().CreateVehicle();
        }

        [Test]
        public void Should_create_a_Nissan_370Z()
        {
            Assert.That(summerVehicle.MakeModel, Is.EqualTo("Nissan 370Z Coupe"));
        }

        [Test]
        public void Should_have_a_standard_steering_wheel()
        {
            Assert.That(summerVehicle.SteeringWheel, Is.EqualTo("Standard Steering Wheel"));
        }

        [Test]
        public void Should_have_all_season_tires()
        {
            Assert.That(summerVehicle.Tires, Is.EqualTo("All Season Tires"));
        }

        [Test]
        public void Should_have_a_V6_engine()
        {
            Assert.That(summerVehicle.Engine, Is.EqualTo("3.7L V6"));
        }

        [Test]
        public void Should_have_leather_seats()
        {
            Assert.That(summerVehicle.Seats, Is.EqualTo("Leather Bucket Seats"));
        }

        [Test]
        public void Should_have_a_premium_sound_system()
        {
            Assert.That(summerVehicle.Amenities.Contains("Premium Sound System"));
        }

        [Test]
        public void Should_not_have_any_other_amenities()
        {
            Assert.That(summerVehicle.Amenities.Count, Is.EqualTo(1));
        }
    }
}

Ruby Example

Now let’s create the same vehicle class in Ruby. Really impressed with how much smaller this class is!

class Vehicle
  attr_accessor :steering_wheel, :tires, :seats
  attr_accessor :amenities, :engine, :make_model

  def initialize
    @amenities = []
  end
end

Next, we’ll create our base vehicle factory class. It’s worth noting that Ruby doesn’t have an “abstract” keyword. To get the same effect, we’ll raise an exception if an expected method was not overridden in a subclass.

require 'lib/Vehicle'

class VehicleFactoryBase
  def create_vehicle
    @vehicle_under_construction = Vehicle.new
    add_steering_wheel
    add_tires
    add_seats
    add_amenities
    add_engine
    add_make_model
    @vehicle_under_construction
  end

  protected
  
  def add_steering_wheel
    @vehicle_under_construction.steering_wheel = "Standard Steering Wheel"
  end

  def add_tires
    @vehicle_under_construction.tires = "All Season Tires"
  end

  def add_seats
    @vehicle_under_construction.seats = "Cloth Bucket Seats"
  end

  def add_amenities
  end

  def add_engine
    raise "subclass must include the logic for setting the engine"
  end

  def add_make_model
    raise "subclass must include the logic for setting the make and model"
  end
end

And here’s our first subclass, the winter vehicle factory.

require 'lib/Vehicle'
require 'lib/VehicleFactoryBase'

class WinterVehicleFactory < VehicleFactoryBase 
    def add_tires
      @vehicle_under_construction.tires = "Snow Tires"
    end

    def add_amenities
      @vehicle_under_construction.amenities.push "4 Wheel Drive"
      @vehicle_under_construction.amenities.push "Snow Plow"
    end

    def add_engine
      @vehicle_under_construction.engine = "4.7L V-8"
    end

    def add_make_model
      @vehicle_under_construction.make_model = "Jeep Grand Cherokee"
    end
end

Next up is the summer vehicle factory.

require 'lib/Vehicle'
require 'lib/VehicleFactoryBase'

class SummerVehicleFactory < VehicleFactoryBase
    def add_seats
      @vehicle_under_construction.seats = "Leather Bucket Seats"
    end

    def add_amenities
      @vehicle_under_construction.amenities.push "Premium Sound System"
    end

    def add_engine
      @vehicle_under_construction.engine = "3.7L V6"
    end

    def add_make_model
      @vehicle_under_construction.make_model = "Nissan 370Z Coupe"
    end
end

And of course our unit tests.

require 'rubygems'
require "spec"
require 'lib/WinterVehicleFactory'

describe "A newly created vehicle" do
  before(:each) do
    @winter_vehicle = WinterVehicleFactory.new.create_vehicle
  end

  it "should be a Jeep Grand Cherokee" do
    @winter_vehicle.make_model.should == "Jeep Grand Cherokee"
  end

  it "should have a standard steering wheel" do
    @winter_vehicle.steering_wheel.should == "Standard Steering Wheel"
  end

  it "should have snow tires" do
    @winter_vehicle.tires.should == "Snow Tires"
  end

  it "should have a V8 engine" do
    @winter_vehicle.engine.should == "4.7L V-8"
  end

  it "should have standard cloth seats" do
    @winter_vehicle.seats.should == "Cloth Bucket Seats"
  end

  it "should have 4 wheel drive" do
    @winter_vehicle.amenities.should include("4 Wheel Drive")
  end

  it "should have a snow plow" do
    @winter_vehicle.amenities.should include("Snow Plow")
  end

  it "should not have any other amenities" do
    @winter_vehicle.amenities.length.should == 2
  end
end
require 'rubygems'
require "spec"
require 'lib/SummerVehicleFactory'

describe "When creating a summer cruiser" do
  before(:each) do
    @summer_cruiser = SummerVehicleFactory.new.create_vehicle
  end

  it "should be a Nissan 370Z" do
    @summer_cruiser.make_model.should == "Nissan 370Z Coupe"
  end

  it "should have a standard steering wheel" do
    @summer_cruiser.steering_wheel.should == "Standard Steering Wheel"
  end

  it "should have all season tires" do
    @summer_cruiser.tires.should == "All Season Tires"
  end

  it "should have a V6 engine" do
    @summer_cruiser.engine.should == "3.7L V6"
  end

  it "should have leather seats" do
    @summer_cruiser.seats.should == "Leather Bucket Seats"
  end

  it "should have a premium sound system" do
    @summer_cruiser.amenities.should include("Premium Sound System")
  end

  it "should not have any other amenities" do
    @summer_cruiser.amenities.length.should == 1
  end
end

In the next post we’ll look at the strategy pattern. Stay tuned!
kick it on DotNetKicks.com