Menu
Coddy logo textTech

Decorator-Muster

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

Das Decorator-Muster verpackt ein Objekt in ein anderes Objekt mit derselben Schnittstelle, das etwas hinzufügt, bevor der Aufruf weitergeleitet wird, oder danach. Decorators 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 stellt mit SimpleDelegator (require 'delegate') einen Delegator bereit, der jede Methode, die er nicht selbst definiert, an das umschlossene Objekt weiterleitet. Dadurch bleiben Dekoratoren 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 können frei kombiniert werden. Bei Unterklassen bräuchte jede Kombination wie MilkSugarCoffee eine eigene Klasse:

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

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

Aufgabe

Einfach

Vervollständige die Kaffee-Dekoratoren. 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 Dekoratoren teilen sich die Basisklasse Extra, die das umschlossene Getränk speichert und cost sowie description weiterleitet; jede Unterklasse verwendet super.

Jede Eingabezeile ist eine Bestellung wie milk sugar. Der bereitgestellte Code umschließt ein neues Coffee in dieser Reihenfolge mit den Dekoratoren 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 Eingabe-/Ausgabecode und kann nicht bearbeitet werden.

Probier es selbst

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

# Gelieferter Input/Output-Code: belasse ihn wie er ist
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