Rubyによるデザインパターンまとめ : Decorator
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 13人 クリック: 220回
- この商品を含むブログ (66件) を見る
利用する場面
あるオブジェクトのメソッドについて、特定の場合に処理内容を付加したい。
あれこれ
- たぶんコードとしてはadapterと概ね同じ。違う(ことが多い)のは以下の3点。
- 意図
- adapter: あるオブジェクトを別のオブジェクトから呼び出せるようにインターフェースを変換したい
- decorator: あるオブジェクトに装飾的に機能を追加したい(1回のdecorateで済ませるのではなく、チェーンすることでレイヤ状に重ねて装飾したい。)
- 元のオブジェクトのメソッドを維持するか
- adapterはその意図ゆえ元のオブジェクトがもつメソッドを持たせないことが多い(接続さえできればいい)
- 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クラスのインスタンスを新たにつくる」だったが、「元のオブジェクトはそのままに、1つのインスタンスにどんどんdecoretorモジュールをextendで付加していく」というやり方。
やたらと新しいインスタンスを増やさなくていい代わりに、元のインスタンスからモジュールを取り除くことができないので、「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!!!"