Menu
Coddy logo textTech

デコレーターパターン

CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 47/57。

デコレータパターンは、同じインターフェースを持つ別のオブジェクトでオブジェクトをラップし、呼び出しを渡す前または後に何かを追加します。デコレータは任意の順序で積み重ねることができます。

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)

出力:

coffee, milk, milk: 3.00

すべてのデコレーターはラップしたオブジェクトを保持し、そのオブジェクトに処理を委譲するため、その部分を共有基底クラスに持たせることができます。各具象デコレーターは、変更する部分だけをオーバーライドします。

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)

出力:

coffee, sugar: 2.20

標準ライブラリの SimpleDelegator(require 'delegate')は、定義していないすべてのメソッドをラップされたオブジェクトに転送するため、インターフェースが大きい場合でもデコレーターを短く保てます。

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

出力:

HELLO!
5
true

デコレータは実行時に単一のオブジェクトへ振る舞いを追加でき、自由に組み合わせることができます。サブクラスを使う場合、MilkSugarCoffee のような組み合わせごとに、それ専用のクラスが必要になります。

Sugar.new(Milk.new(Coffee.new))      # 任意の組み合わせ、任意の順序
Whip.new(Sugar.new(Coffee.new))

# サブクラスを使用: MilkCoffee, SugarCoffee, MilkSugarCoffee, ...
challenge icon

チャレンジ

簡単

コーヒーのデコレーターを完成させてください。Coffeeのコストは2.00で、説明はcoffeeです。Milkは0.50と, milkを追加し、Sugarは0.20と, sugarを追加し、Whipは0.70と, whipを追加します。3つのデコレーターは基底クラスExtraを共有します。このクラスはラップされたドリンクを保持し、costとdescriptionを転送します。各サブクラスではsuperを使用します。

各入力行はmilk sugarのような注文です。提供されたコードは、新しいCoffeeをその順序でデコレーターでラップし、coffee, milk, sugar: 2.70を出力します。

コードはcoffee.rb、extra.rb、milk.rb、sugar.rb、whip.rbに記述します。main.rbには提供された入出力コードが含まれており、編集できません。

自分で試してみよう

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

# 提供された入出力コード: そのままにしておく
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 icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ