Menu
Coddy logo textTech

Шаблон строителя

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 48 из 57.

Паттерн строитель создаёт сложный объект шаг за шагом. Каждый шаг представляет собой метод, который сохраняет один вариант и возвращает строитель, поэтому шаги образуют цепочку; build проверяет выбранные варианты и возвращает готовый объект:

class Pizza
  def initialize(size, toppings)
    @size = size
    @toppings = toppings
  end

  def to_s
    "#{@size} pizza with #{@toppings.join(", ")}"
  end
end

class PizzaBuilder
  def initialize
    @toppings = []
  end

  def size(value)
    @size = value
    self
  end

  def topping(name)
    @toppings << name
    self
  end

  def build
    raise ArgumentError, "size is required" if @size.nil?

    Pizza.new(@size, @toppings)
  end
end

puts PizzaBuilder.new.size("large").topping("cheese").topping("ham").build

Результат:

large pizza with cheese, ham

Поскольку строитель создаёт объект только в build, он может сразу проверить всю комбинацию, например обязательную часть или два параметра, которые нельзя использовать вместе:

class TicketBuilder
  def seat(value)
    @seat = value
    self
  end

  def standing
    @standing = true
    self
  end

  def build
    raise ArgumentError, "seat and standing exclude each other" if @seat && @standing

    @standing ? "standing ticket" : "ticket for seat #{@seat || "any"}"
  end
end

puts TicketBuilder.new.seat("B7").build
puts TicketBuilder.new.standing.build
begin
  TicketBuilder.new.seat("A1").standing.build
rescue ArgumentError => e
  puts e.message
end

Вывод:

ticket for seat B7
standing ticket
seat and standing exclude each other

Распространённая форма Ruby передаёт построитель блоку. Метод класса создаёт построитель, передаёт его в блок и в конце вызывает build:

class Email
  attr_accessor :to, :subject, :lines

  def self.compose
    email = new
    email.lines = []
    yield email
    email
  end

  def to_s
    "To: #{to}\nSubject: #{subject}\n\n#{lines.join("\n")}"
  end
end

mail = Email.compose do |e|
  e.to = "ada@x.io"
  e.subject = "Hi"
  e.lines << "Hello Ada,"
  e.lines << "see you soon."
end
puts mail

Вывод:

To: ada@x.io
Subject: Hi

Hello Ada,
see you soon.

Для небольшого числа необязательных значений часто достаточно ключевых аргументов Ruby со значениями по умолчанию. Билдер оправдан, когда части могут повторяться, важен порядок или комбинацию нужно проверить до создания объекта:

Pizza.new(size: "large", crust: "thin")          # несколько опций: ключевые слова

Query.new.from("users").where("age > 18").where("active = 1").limit(10).to_s
challenge icon

Задание

Легко

Завершите PizzaBuilder и Pizza. Шаги построителя size(value), crust(value) и topping(name) (который можно повторять) возвращают построитель. build вызывает исключение ArgumentError с сообщением size is required, если размер не указан, использует корж classic, если он не выбран, и возвращает объект Pizza. Pizza#to_s возвращает large thin pizza with cheese, ham или small classic pizza with no toppings.

Каждая входная строка представляет собой один заказ, например size=large crust=thin topping=cheese topping=ham. Предоставленный код по очереди передаёт каждую часть построителю, связывая вызовы в цепочку, и выводит пиццу или сообщение об ошибке.

Ваш код должен находиться в pizza.rb и pizza_builder.rb. В main.rb содержится предоставленный код ввода/вывода, и его нельзя изменять.

Попробуйте сами

require_relative 'pizza'
require_relative 'pizza_builder'

# Предоставленный код ввода/вывода: оставьте его как есть
input = []
while (line = gets)
  input << line.chomp
end
input.each do |line|
  builder = line.split.reduce(PizzaBuilder.new) do |b, part|
    step, value = part.split("=")
    b.public_send(step, value)
  end
  begin
    puts builder.build
  rescue ArgumentError => e
    puts e.message
  end
end
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Ruby