Menu
Coddy logo textTech

Padrão Decorator

Parte da seção Programação orientada a objetos do Journey de Ruby da Coddy. Lição 47 de 57.

O padrão decorator envolve um objeto em outro objeto com a mesma interface, que adiciona algo antes ou depois de encaminhar a chamada. Os decorators podem ser empilhados em qualquer ordem:

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)

Saída:

coffee, milk, milk: 3.00

Cada decorador armazena o objeto encapsulado e encaminha as chamadas para ele, portanto uma classe base compartilhada pode conter essa parte. Cada decorador concreto então sobrescreve apenas o que altera:

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)

Saída:

coffee, sugar: 2.20

A biblioteca padrão SimpleDelegator (require 'delegate') encaminha todos os métodos que não define para o objeto encapsulado, o que mantém os decoradores curtos quando a interface é grande:

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")

Saída:

HELLO!
5
true

Decoradores adicionam comportamento a objetos individuais em tempo de execução e podem ser combinados livremente. Com subclasses, cada combinação, como MilkSugarCoffee, precisaria de uma classe própria:

Sugar.new(Milk.new(Coffee.new))      # qualquer combinação, qualquer ordem
Whip.new(Sugar.new(Coffee.new))

# com subclasses: MilkCoffee, SugarCoffee, MilkSugarCoffee, ...
challenge icon

Desafio

Fácil

Complete os decoradores de café. Coffee custa 2.00 e é descrito como coffee. Milk adiciona 0.50 e , milk, Sugar adiciona 0.20 e , sugar, e Whip adiciona 0.70 e , whip. Os três decoradores compartilham a classe-base Extra, que armazena a bebida envolvida e encaminha cost e description; cada subclasse usa super.

Cada linha de entrada é um pedido, como milk sugar. O código fornecido envolve um novo Coffee nos decoradores nessa ordem e imprime coffee, milk, sugar: 2.70.

Seu código deve estar em coffee.rb, extra.rb, milk.rb, sugar.rb e whip.rb. main.rb contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

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

# Código de entrada/saída fornecido: mantenha-o como está
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 iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de Ruby online