Das Decorator Entwurfsmuster

Das decorator pattern erlaubt es, ein Objekt dynamisch mit zusätzlicher Funktionalität anzureichern. So kann in vielen Fällen ausufernde Vererbung vermieden werden.
Dekorierer sind außerdem sinnvoll, wenn Klassen dem Single Responsibility Principle entsprechen sollen, weil es erlaubt, Funktionalität auf verschiedene Klassen aufzuteilen.
Man stelle sich zum Beispiel einen Tisch vor, der dekoriert werden kann. Wenn der Tisch gedeckt wird, wird daraus ein Esstisch. Wenn ein Laptop drauf gestellt wird, ist es eben eine Arbeitsstation.
Ein anderes einfaches Beispiel sind Produkte, die einen Preis haben:

class Product
  attr_accessor :price

  def initialize price
    @price = price
  end
end

Der Gesamtpreis des jeweiligen Produktes berechnet sich unterschiedlich, abhängig vom Kontext. Ausgehend vom Grundpreis, können Rabatte gewährt werden, müssen Steuern aufgeschlagen werden und so weiter.
Sicherlich könnte man das mit etwas Logik in der Product Klasse abbilden oder aber entsprechende Produktklassen von Product ableiten, allerdings wird es dann sehr schnell unübersichtlich.
Sinnvoller ist es, die Produkt Klasse so zu belassen und stattdessen decorators für den jeweiligen Kontext zu nutzen:

class TaxDecorator
  TAX_FACTOR = 1.2

  def initialize object
    @object = object
  end

  def price
    @object.price * TAX_FACTOR
  end
end

class DiscountDecorator
  attr_reader :discount_factor
  DISCOUNT_FACTOR = 0.9

  def initialize object
    @object = object
  end

  def price
    @object.price * discount_factor
  end

private
  def discount_factor
    @discount_factor || DEFAULT_DISCOUNT_FACTOR
  end
end

Wichtig ist es, dass das interface der decorators dem des Zielobjektes (das Produkt) entspricht, um die volle Funktionalität zu erhalten.
Nun kann der Preis eines Produktes dynamisch geändert werden:

shirt = Product.new 10
shirt.price # => 10
TaxDecorator.new(shirt).price # => 12.0
DiscountDecorator.new(shirt).price # => 9.0

Im Grunde umhüllt jeder decorator das Zielobjekt und fügt neues Verhalten hinzu.
Natürlich können decorators auch miteinander kombiniert werden:

tv = Product.new 200
TaxDecorator.new(DiscountDecorator.new tv).price # => 216.0

Auch andere Klassen können dekoriert werden, wenn sie dem erwarteten interface entsprechen:

class Employee
  def initialize name, annual_salary
    @name = name
    @salary = annual_salary
  end

  def price
    @salary / 1600 # ca. 1.600 Arbeitsstunden hat das Jahr
  end
end

Die Arbeitsstunde von Bob kostet dann also mindestens:

bob = Employee.new 'Bob', 50000
bob.price # => 31.25
TaxDecorator.new(bob).price # => 37.2