Rubyによるデザインパターンまとめ : Decorator

Rubyによるデザインパターン

Rubyによるデザインパターン

利用する場面

あるオブジェクトのメソッドについて、特定の場合に処理内容を付加したい。

あれこれ

  • たぶんコードとしてはadapterと概ね同じ。違う(ことが多い)のは以下の3点。
    • 意図
      • adapter: あるオブジェクトを別のオブジェクトから呼び出せるようにインターフェースを変換したい
      • decorator: あるオブジェクトに装飾的に機能を追加したい(1回のdecorateで済ませるのではなく、チェーンすることでレイヤ状に重ねて装飾したい。)
    • 元のオブジェクトのメソッドを維持するか
      • adapterはその意図ゆえ元のオブジェクトがもつメソッドを持たせないことが多い(接続さえできればいい)
      • decoratorは基本的に元のオブジェクトと同じように振る舞わせたいので、メソッドを完全に引き継がせる。そのために継承なり委譲なりをつかうことになる。
    • 付加するのは既存メソッドの中身か、新しいメソッドか
      • adapterは新しいメソッドを付加する
      • decoratorは主として既存メソッドの中身を付加する(オーバーライド)
        • 「それぞれのDecoratorは、基底のコンポーネントの仕事の上に各自の特別なマジックを重ね、それぞれの能力を少なくとも1つのメソッドに追加します。Decoratorは新しいメソッド、すなわちComponentインターフェイスで定義されていない操作を追加することもできますが、この振る舞いはオプションです。」

サンプル

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