Menu
CoddyTech

Decorator-Muster

Teil des Abschnitts Objektorientierte Programmierung der Ruby-Journey von Coddy. Lektion 47 von 57.

Das Dekorator-Muster umhüllt ein Objekt mit einem anderen Objekt mit derselben Schnittstelle und fügt etwas hinzu, bevor der Aufruf weitergegeben wird oder nachdem dies geschehen ist. Dekoratoren können in beliebiger Reihenfolge gestapelt werden:

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)

Ausgabe:

coffee, milk, milk: 3.00

Jeder Decorator speichert das umschlossene Objekt und leitet Aufrufe an es weiter, daher kann eine gemeinsame Basisklasse diesen Teil übernehmen. Jeder konkrete Decorator überschreibt dann nur das, was er ändert:

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)

Ausgabe:

coffee, sugar: 2.20

Die Standardbibliothek SimpleDelegator (require 'delegate') leitet jede Methode, die sie nicht selbst definiert, an das umschlossene Objekt weiter. Dadurch bleiben Decorators kurz, wenn die Schnittstelle umfangreich ist:

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

Ausgabe:

HELLO!
5
true

Dekoratoren fügen einzelnen Objekten zur Laufzeit Verhalten hinzu und lassen sich beliebig kombinieren. Bei Unterklassen müsste es für jede Kombination eine eigene Klasse geben, zum Beispiel MilkSugarCoffee:

Sugar.new(Milk.new(Coffee.new))      # jede Kombination, jede Reihenfolge
Whip.new(Sugar.new(Coffee.new))

# mit Unterklassen: MilkCoffee, SugarCoffee, MilkSugarCoffee, ...
challenge icon

Aufgabe

Einfach

Vervollständige die Kaffee-Decoratoren. Coffee kostet 2.00 und wird als coffee beschrieben. Milk fügt 0.50 und , milk hinzu, Sugar fügt 0.20 und , sugar hinzu und Whip fügt 0.70 und , whip hinzu. Die drei Decoratoren verwenden die Basisklasse Extra, die das verpackte Getränk speichert und cost und description weiterleitet; jede Unterklasse verwendet super.

Jede Eingabezeile ist eine Bestellung wie milk sugar. Der bereitgestellte Code verpackt ein neues Coffee in dieser Reihenfolge in die Decoratoren und gibt coffee, milk, sugar: 2.70 aus.

Dein Code gehört in coffee.rb, extra.rb, milk.rb, sugar.rb und whip.rb. main.rb enthält den bereitgestellten Ein-/Ausgabecode und kann nicht bearbeitet werden.

Probier es selbst

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

# Vorgegebener Eingabe-/Ausgabecode: unverändert lassen
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 dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Ruby-Compiler