Menu
Coddy logo textTech

Фабричный паттерн

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 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