デコレーターパターン
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, ...チャレンジ
簡単コーヒーのデコレーターを完成させてください。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
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ