Menu
Coddy logo textTech

Шаблон декоратора

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

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

class Coffee
  def cost
    2.0
  end

  def description
    "coffee"
  end
end

class Milk
  def initialize(drink)
    @drink = drink
  end

  def cost
    @drink.cost + 0.5
  end

  def description
    "#{@drink.description}, milk"
  end
end

order = Milk.new(Milk.new(Coffee.new))
puts format("%s: %.2f", order.description, order.cost)

Вывод:

coffee, milk, milk: 3.00

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

class Coffee
  def cost
    2.0
  end

  def description
    "coffee"
  end
end

class Extra
  def initialize(drink)
    @drink = drink
  end

  def cost
    @drink.cost
  end

  def description
    @drink.description
  end
end

class Sugar < Extra
  def cost
    super + 0.2
  end

  def description
    super + ", sugar"
  end
end

order = Sugar.new(Coffee.new)
puts format("%s: %.2f", order.description, order.cost)

Вывод:

coffee, sugar: 2.20

Стандартная библиотека SimpleDelegator (require 'delegate') перенаправляет каждый метод, который она не определяет, обёрнутому объекту, благодаря чему декораторы остаются короткими даже при большом интерфейсе:

require 'delegate'

class Loud < SimpleDelegator
  def to_s
    super.upcase + "!"
  end
end

text = Loud.new("hello")
puts text
puts text.size
puts text.start_with?("he")

Вывод:

HELLO!
5
true

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

Sugar.new(Milk.new(Coffee.new))      # любая комбинация, любой порядок
Whip.new(Sugar.new(Coffee.new))

# с подклассами: MilkCoffee, SugarCoffee, MilkSugarCoffee, ...
challenge icon

Задание

Легко

Завершите декораторы кофе. Coffee стоит 2.00 и описывается как coffee. Milk добавляет 0.50 и , milk, Sugar добавляет 0.20 и , sugar, а Whip добавляет 0.70 и , whip. Три декоратора используют общий базовый класс Extra, который хранит обёрнутый напиток и передаёт вызовы cost и description; каждый подкласс использует super.

Каждая входная строка представляет собой заказ, например milk sugar. Предоставленный код оборачивает новый объект Coffee в декораторы в указанном порядке и выводит coffee, milk, sugar: 2.70.

Ваш код должен находиться в файлах coffee.rb, extra.rb, milk.rb, sugar.rb и whip.rb. В main.rb находится предоставленный код ввода/вывода, и его нельзя редактировать.

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

require_relative 'coffee'
require_relative 'extra'
require_relative 'milk'
require_relative 'sugar'
require_relative 'whip'

# Предоставленный код ввода/вывода: оставьте его как есть
input = []
while (line = gets)
  input << line.chomp
end
extras = { "milk" => Milk, "sugar" => Sugar, "whip" => Whip }
input.each do |line|
  drink = line.split.reduce(Coffee.new) { |d, name| extras[name].new(d) }
  puts format("%s: %.2f", drink.description, drink.cost)
end
quiz iconПроверьте себя

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

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

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