2019-05-09
Rubyによるデザインパターン - decorator
[](http://www.amazon.co.jp/exec/obidos/ASIN/4894712857/hatena- blog-22/)
利用する場面
あるオブジェクトのメソッドについて、特定の場合に処理内容を付加したい。
あれこれ
- たぶんコードとしては adapter と概ね同じ。違う(ことが多い)のは以下の 3 点。
- 意図
- adapter: あるオブジェクトを別のオブジェクトから呼び出せるようにインターフェースを変換したい
- decorator: あるオブジェクトに装飾的に機能を追加したい(1 回の decorate で済ませるのではなく、チェーンすることでレイヤ状に重ねて装飾したい。)
- 元のオブジェクトのメソッドを維持するか
- adapter はその意図ゆえ元のオブジェクトがもつメソッドを持たせないことが多い(接続さえできればいい)
- decorator は基本的に元のオブジェクトと同じように振る舞わせたいので、メソッドを完全に引き継がせる。そのために継承なり委譲なりをつかうことになる。
- 付加するのは既存メソッドの中身か、新しいメソッドか
- adapter は新しいメソッドを付加する
- decorator は主として既存メソッドの中身を付加する(オーバーライド)
- 「それぞれの Decorator は、基底のインターフェイスで定義されていない操作を追加することもできますが、この振る舞いはオプションです。」
- 意図
サンプル
decorator に元のオブジェクトのメソッドを持たせる方法がいくつかある。
まずは decorator に元のオブジェクトを継承させる方法。
class TextProcessor
def describe
"this is a text processor."
end
def execute(text)
text
end
end
class TextDecorator < TextProcessor
def initialize(original_processor)
@original_processor = original_processor
end
end
class Capitalizer < TextDecorator
def execute(text)
@original_processor.execute(text).upcase
end
end
class Exclaimer < TextDecorator
def execute(text)
@original_processor.execute(text) << "!!!"
end
end
text_processor = TextProcessor.new
p text_processor.describe # => "this is a text processor."
p text_processor.execute("hoge") # => "hoge"
text_processor = Capitalizer.new(text_processor)
p text_processor.describe # => "this is a text processor."
p text_processor.execute("hoge") # => "HOGE"
text_processor = Exclaimer.new(text_processor)
p text_processor.describe # => "this is a text processor."
p text_processor.execute("hoge") # => "HOGE!!!"
元のオブジェクトの機能(describe メソッドとか)を損なうことなく、execute メソッドに処理を付加することができた。
次は decorator から元のオブジェクトに委譲する方法。 TextDecorator クラスだけ以下のように書き換える。
class TextDecorator
def initialize(original_processor)
@original_processor = original_processor
end
def describe
@original_processor.describe
end
end
この委譲を、forwardable モジュールをつかってよりシンプルに実現する。 いちいち中身が 1 行だけの委譲用メソッドを書かなくて済む。
class TextDecorator
extend Forwardable
def_delegators :@original_processor, :describe
def initialize(original_processor)
@original_processor = original_processor
end
end
最後に、module をつかう方法。
これまでは「元のオブジェクトとほとんど同じ振る舞いをする decorator クラスを用意して、decorate するときは decorator クラスのインスタンスにどんどん decoretor モジュールを extend で付加していく」というやり方。
やたらと新しいインスタンスを使いたい」が叶わなくなってしまうのが欠点。
class TextProcessor
def describe
"this is a text processor."
end
def execute(text)
text
end
end
module Capitalizable
def execute(text)
super.upcase
end
end
module Exclaimable
def execute(text)
super << "!!!"
end
end
text_processor = TextProcessor.new
p text_processor.describe # => "this is a text processor."
p text_processor.execute("hoge") # => "hoge"
text_processor.extend(Capitalizable)
p text_processor.describe # => "this is a text processor."
p text_processor.execute("hoge") # => "HOGE"
text_processor.extend(Exclaimable)
p text_processor.describe # => "this is a text processor."
p text_processor.execute("hoge") # => "HOGE!!!"