Menu
CoddyTech

工厂模式

本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 43 课,共 57 课。

工厂模式将对象创建集中在一个地方。调用方按需请求,通常通过名称指定,并获得一个现成的对象,而不必知道具体创建了哪个类:

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

输出:

9
nil

将名称映射到类的哈希表可以让工厂更简短,也更容易扩展。类是对象,因此可以存储在哈希表中,并调用 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

输出:

Meow
unknown animal: cow

工厂也可以让其他代码在运行时注册新的类型,这样添加类型就不需要修改工厂:

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

输出:

zipping
["zip"]

调用方只依赖对象能做什么,这里是 area 或 speak,而不依赖具体的类。新增一种形状,只需新增一个类,并在工厂中添加一条记录,其他地方都不需要修改:

shape = ShapeFactory.create(kind, size)   # Square, Circle, ...
puts shape.area                           # 调用方从不指定类名
challenge icon

挑战

简单

完成这些形状和 ShapeFactory.create(kind, size)。Square、Circle 和 Triangle(等边)共享基类 Shape,其 to_s 会返回 square 3: area 9.00,其中包含小写的类名、尺寸和保留两位小数的面积。圆周率使用 3.14159,三角形使用 Math.sqrt(3) / 4 * size**2 计算面积。工厂会在冻结的哈希常量 KINDS 中查找形状类型,遇到未知类型时返回 nil。

提供的代码会读取 square 3 这样的行,为每个形状调用工厂并打印结果,否则打印 unknown shape: hexagon。

你的代码要写在 shape.rb、square.rb、circle.rb、triangle.rb 和 shape_factory.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。

亲手试试

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

# 提供的输入/输出代码:保持原样
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 icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Ruby 在线编译器