Creating An Abstract Factory (Modern and Victorian Furniture Maker) in Ruby

16 Jun 2020

The c2Wiki has a Roadmap for learning design patterns. I'm going to start by looking at each of the creational design patterns, and note what the purpose of each of them are.

Creational Design patterns: Brief intent overview

Abstract Factory Pattern in Ruby

Source: Refactoring.guru

Abstract factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

I went ahead and played around with this, and attempted to create my own version of abstract factory, using a furniture shop as an example. The final application creates one of each type of furniture. I'll put all my code below:

Modern Chair

The modern chair class has two methods, .describe_legs and .describe_sitting experience. The chair also has a name to reflect it's type. Same for the victorian chair.

I used the RSpec testing suites 'respond_to' method as an interface replacement, to make it explicit which methods the chair is supposed to implement.


require "modern_chair"

describe "modern_chair" do
  let(:modern_chair) { ModernChair.new }
  it { expect(modern_chair).to respond_to(:describe_legs) }
  it { expect(modern_chair).to respond_to(:describe_sitting_experience) }

  describe ".describe_legs" do
    it "has 3 bamboo legs" do
      expect(modern_chair.describe_legs).to eq("3 legs made of cream bamboo")
    end
  end

  describe ".describe_sitting_experience" do
    it "Like being wrapped in a cocoon" do
      expect(modern_chair.describe_sitting_experience).to eq("This round chair feels like being wrapped up in a cocoon")
    end
  end
end


class ModernChair
  attr_reader :name

  def initialize
    @name = "Modern Chair"
  end

  def describe_legs
    "3 legs made of cream bamboo"
  end

  def describe_sitting_experience
    "This round chair feels like being wrapped up in a cocoon"
  end
end

Modern Coffee Table

The modern coffee table has one method called .describe_surface. Same for the victorian chair.


require "modern_coffee_table"

describe "modern_coffee_table" do
  let(:modern_coffee_table) { ModernCoffeeTable.new }

  it { expect(modern_coffee_table).to respond_to(:describe_surface) }

  describe ".describe_table_surface" do
    it "Has a glass surface" do
      expect(modern_coffee_table.describe_surface).to eq("Glass surface. Funny when cats sit on it.")
    end
  end
end


class ModernCoffeeTable
  attr_reader :name

  def initialize
    @name = "Modern Coffee Table"
  end

  def describe_surface
    "Glass surface. Funny when cats sit on it."
  end
end

Modern Sofa

The modern sofa has one method, .describe_fabric. Same for the Victorian sofa.


require "modern_sofa"

describe "modern_sofa" do
  let(:modern_sofa) { ModernSofa.new }

  it { expect(modern_sofa).to respond_to(:describe_fabric) }

  describe ".describe_fabric" do
    it "Made from cotton" do
      expect(modern_sofa.describe_fabric).to eq("Made from cotton.")
    end
  end
end


class ModernSofa
  attr_reader :name

  def initialize
    @name = "Modern Sofa"
  end

  def describe_fabric
    "Made from cotton."
  end
end

Modern Furniture Factory

The modern furniture factory has three methods, which all create an instance of each of the furniture type classes we have already created (Chair, coffee table and sofa). Same for the victorian furniture factory.


require "modern_furniture_factory"

describe "modern_furniture_factory" do
  let(:modernff) { ModernFurnitureFactory.new }

  it { expect(modernff).to respond_to(:create_chair) }
  it { expect(modernff).to respond_to(:create_coffee_table) }
  it { expect(modernff).to respond_to(:create_sofa) }

  describe ".create_chair" do
    it "creates modern chairs" do
      expect(modernff.create_chair).to be_an_instance_of(ModernChair)
    end
  end

  describe ".create_coffee_table" do
    it "creates modern coffee tables" do
      expect(modernff.create_coffee_table).to be_an_instance_of(ModernCoffeeTable)
    end
  end

  describe ".create_sofa" do
    it "creates modern sofas" do
      expect(modernff.create_sofa).to be_an_instance_of(ModernSofa)
    end
  end
end

Furniture Factory

The furniture factory can be initialized with a specific furniture factory type. I think this is a bit redundant and maybe I misunderstood the example. It would be cool for this to have a factory type list, which you can add to or delete factories from. Then a GUI where you can swap out factories on the fly.


class FurnitureFactory
  attr_reader :factory

  def initialize(factory = ModernFurnitureFactory.new)
    @factory = factory
  end
end

Application

The application code has a factory type store, then loops through the factories and creates each of the furniture types which are consistent for each of these categories. I think this code here is a little too procedural, but not sure. It seems that the abstract factory pattern is used for reskinning objects into a different style, because this wouldn't work if one of the furniture styles had an extra piece of furniture that the other furniture factories didn't have. Or maybe it would, not sure.


require_relative 'furniture_factory'
require_relative 'modern_furniture_factory'
require_relative 'victorian_furniture_factory'

factories = [(FurnitureFactory.new).factory, (FurnitureFactory.new(VictorianFurnitureFactory.new)).factory]


factories.each do |f|
  # Create Chairs
  chair = f.create_chair
  puts chair.name.upcase
  puts "- #{chair.describe_legs}"
  puts "- #{chair.describe_sitting_experience} \n\n"

  #Create Coffee Tables
  coffee_table = f.create_coffee_table
  puts coffee_table.name.upcase
  puts "- #{coffee_table.describe_surface} \n\n"

  #Create Sofas
  sofa = f.create_sofa
  puts sofa.name.upcase
  puts "- #{sofa.describe_fabric} \n\n"
end

This was fun. I want to find a couple more examples of the abstract factory pattern so I can play around with it some more and maybe come up with some different use-cases.