Menu
Coddy logo textTech

Patrón Decorator

Parte de la sección Programación orientada a objetos del Journey de Ruby de Coddy. Lección 47 de 57.

El patrón decorador envuelve un objeto en otro objeto con la misma interfaz, que añade algo antes o después de pasar la llamada. Los decoradores se pueden apilar en cualquier orden:

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)

Salida:

coffee, milk, milk: 3.00

Cada decorador almacena el objeto envuelto y delega en él, por lo que una clase base compartida puede encargarse de esa parte. Después, cada decorador concreto sobrescribe únicamente lo que cambia:

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)

Salida:

coffee, sugar: 2.20

La biblioteca estándar SimpleDelegator (require 'delegate') reenvía todos los métodos que no define al objeto envuelto, lo que permite mantener breves los decoradores cuando la interfaz es 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")

Salida:

HELLO!
5
true

Los decoradores añaden comportamiento a objetos individuales en tiempo de ejecución y se pueden combinar libremente. Con subclases, cada combinación, como MilkSugarCoffee, necesitaría su propia clase:

Sugar.new(Milk.new(Coffee.new))      # cualquier combinación, cualquier orden
Whip.new(Sugar.new(Coffee.new))

# con subclases: MilkCoffee, SugarCoffee, MilkSugarCoffee, ...
challenge icon

Desafío

Fácil

Completa los decoradores de café. Coffee cuesta 2.00 y se describe como coffee. Milk añade 0.50 y , milk, Sugar añade 0.20 y , sugar, y Whip añade 0.70 y , whip. Los tres decoradores comparten la clase base Extra, que almacena la bebida envuelta y reenvía cost y description; cada subclase usa super.

Cada línea de entrada es un pedido, como milk sugar. El código proporcionado envuelve un nuevo Coffee en los decoradores en ese orden e imprime coffee, milk, sugar: 2.70.

Tu código va en coffee.rb, extra.rb, milk.rb, sugar.rb y whip.rb. main.rb contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

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

# Código de entrada/salida suministrado: déjalo tal 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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Ruby online