Menu
Coddy logo textTech

Factory Pattern

Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 43 of 57.

The factory pattern puts object creation in one place. Callers ask for what they need, often by name, and receive a ready object without knowing which class was built:

class Square
  def initialize(side)
    @side = side
  end

  def area
    @side * @side
  end
end

class Circle
  def initialize(r)
    @r = r
  end

  def area
    (3.14159 * @r * @r).round(2)
  end
end

class ShapeFactory
  def self.create(kind, size)
    case kind
    when "square" then Square.new(size)
    when "circle" then Circle.new(size)
    end
  end
end

puts ShapeFactory.create("square", 3).area
puts ShapeFactory.create("hexagon", 3).inspect

Output:

9
nil

A hash from names to classes makes the factory shorter and easier to extend. Classes are objects, so they can be stored in a hash and sent new:

class Dog
  def speak
    "Woof"
  end
end

class Cat
  def speak
    "Meow"
  end
end

class AnimalFactory
  KINDS = { "dog" => Dog, "cat" => Cat }.freeze

  def self.create(kind)
    klass = KINDS.fetch(kind) { raise ArgumentError, "unknown animal: #{kind}" }
    klass.new
  end
end

puts AnimalFactory.create("cat").speak
begin
  AnimalFactory.create("cow")
rescue ArgumentError => e
  puts e.message
end

Output:

Meow
unknown animal: cow

A factory can also let other code register new kinds at run time, so adding a kind does not mean editing the factory:

class Plugins
  @kinds = {}

  class << self
    def register(name, klass)
      @kinds[name] = klass
    end

    def build(name)
      @kinds.fetch(name).new
    end

    def names
      @kinds.keys.sort
    end
  end
end

class Zip
  def run
    "zipping"
  end
end

Plugins.register("zip", Zip)
puts Plugins.build("zip").run
p Plugins.names

Output:

zipping
["zip"]

The callers depend only on what the objects can do, here area or speak, never on the concrete classes. A new shape means one new class and one new entry in the factory, and no change anywhere else:

shape = ShapeFactory.create(kind, size)   # Square, Circle, ...
puts shape.area                           # the caller never names a class
challenge icon

Challenge

Easy

Complete the shapes and ShapeFactory.create(kind, size). Square, Circle and Triangle (equilateral) share the base class Shape, whose to_s returns square 3: area 9.00, with the class name in lower case, the size and the area with two decimals. Use 3.14159 for pi and Math.sqrt(3) / 4 * size**2 for the triangle. The factory looks the kind up in a frozen hash constant KINDS and returns nil for an unknown kind.

The supplied code reads lines square 3, asks the factory for each shape and prints it, or unknown shape: hexagon.

Your code goes in shape.rb, square.rb, circle.rb, triangle.rb and shape_factory.rb. main.rb holds the supplied input/output code and cannot be edited.

Try it yourself

require_relative 'shape'
require_relative 'square'
require_relative 'circle'
require_relative 'triangle'
require_relative 'shape_factory'

# Supplied input/output code: keep it as it is
input = []
while (line = gets)
  input << line.chomp
end
input.each do |line|
  kind, size = line.split
  shape = ShapeFactory.create(kind, size.to_f)
  puts shape || "unknown shape: #{kind}"
end
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online Ruby compiler