k-tokitoh

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

2019-05-09

[![Rubyによるデザインパターン](https://images-fe.ssl-images- amazon.com/images/I/41PNvUxHtgL.SL160.jpg)](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!!!"