Murayama blog.

プログラミングと、その次の話

Adapter

Rubyによるデザインパターン生活。若干飽きてきたけど続けます。
本日はAdapterパターンを取り上げます。


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

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


Adapterパターンは、インターフェースの不一致を埋めるためのパターンです。
ここでいうインターフェースの不一致というのは、
メソッドのネーミング、シグネチャの不一致のことを意味しています。


例えば、次のようなプログラムがあるとします。
画面に文字列を出力するだけのシンプルなものです。
#今回は本に載ってるプログラムをもろパクリではなく、ちょっとアレンジしています。

class Renderer
  def execute(text_object)
    text_object.print
  end
end

class TextObject
  def print
    puts "TextObject print!"
  end
end

このプログラムは、Rendererクラスのexecuteメソッドの中で、
引数に受け取ったオブジェクトのprintメソッドを呼び出します。
引数のオブジェクトには、TextObjectを渡すことができます。

renderer = Renderer.new
text_object = TextObject.new
renderer.execute(text_object)


実行結果は以下のとおりです。

TextObject print!


次に、Rendererクラスのexecuteメソッドの引数に与えるオブジェクトを、
さきほどのTextObjectからLegacyTextObjectに変更します。
LegacyTextObjectクラスは以下のとおりです。

class LegacyTextObject
  def show_message
    puts "LegacyTextObject show_message!"
  end
end


ここで問題が発生します。
LegacyTextObjectクラスにはprintメソッドが定義されていません。
その代わりに類似したメソッドであるshow_messageが定義されています。
このままの状態で、さきほどと同じようにプログラムを実行するとエラーが発生してしまいます。

renderer = Renderer.new
lto = LegacyTextObject.new
renderer.execute(lto)

実行結果(NoMethodError)

NoMethodError: private method ‘print’ called for #<LegacyTextObject:0x1ce6c>


エラーの原因はLegacyTextObjectクラスにprintメソッドが定義されていないためです。
一番簡単な解決法はLegacyTextObjectクラスのshow_messageメソッドを、
printメソッドに名前を変更することですが、
LegacyTextObjectクラスが既に別のプログラムから利用されている場合は、変更箇所が増えてしまいます。


上記のような問題の解決策となるのがAdapterパターンです。
Adapterパターンでは、インタフェースの不一致を吸収するためのAdapterクラスを作成します。

class LegacyTextObjectAdapter
  def initialize(legacty_text_object)
    @lto = legacty_text_object
  end
  
  def print
    @lto.show_message
  end
end


実行時には、LegacyTextObjectAdapterを使ってLegacyTextObjectをラップします。

renderer = Renderer.new
lto = LegacyTextObject.new
renderer.execute(LegacyTextObjectAdapter.new(lto))


すると上手く動作します。(実行結果です)

LegacyTextObject show_message!


Adapterパターンのクラス図は、以下のようになります。


各クラスの役割は以下のとおりです。

  • Client
    • Targetのメソッドを呼び出すクラス。
  • Target
    • 実行対象となるクラス(インタフェース)。
  • Adapter
    • Targetインタフェースと、Adapteeクラスの差分を埋めるクラス。
  • Adaptee
    • 実際に動作するクラス。


先ほどのプログラムの場合、以下のような構成になります。

  • Client
    • Rendererクラス。
  • Target
    • なし。Rubyの場合はインタフェース不要。*1
  • Adapter
    • LegacyTextObjectAdapterクラス。
  • Adaptee
    • LegacyTextObjectクラス。


以上がAdapterパターンの説明です。
なんですが、この本の面白いところはここからです。


Rubyは動的型付け言語です。
Javaのようにコンパイル時に変数の型をチェックするような仕組みはありません。
そのため、Adapterパターンにおいても、
Ruby特有の、動的型付け言語特有の解決策が存在します。


例えば、実行時にクラスを修正することで、メソッドを追加することができます。
LegacyTextObjectがロード済みの場合、以下のようにクラス自身を再定義することができます。

class LegacyTextObject
  def print
    show_message
  end
end


これにより実行時にprintメソッドが追加されます。
printメソッドの内部では、show_messageメソッドを呼んでいるので、
結果的にshow_messageメソッドが呼び出されることになります。


上記の方法はクラス定義自体に変更が発生します。
LegacyTextObjectクラスのすべてのオブジェクトはprintメソッドを保持することになります。


クラスに直接メソッドを追加するのではなく、
特定のオブジェクトにだけメソッドを追加することもできます。

lto = LegacyTextObject.new
class << lto
  def print
    show_message
  end
end

この方法ではlto変数に格納されているオブジェクトのみがprintメソッドを保持することになります。
Rubyではオブジェクトに固有なメソッドのことを特異メソッド(Singletonメソッド)と呼びます。
特異メソッドは、以下のように実装することもできます。

lto = LegacyTextObject.new
def lto.print
  show_message
end


以上で説明はおしまいです。
Adapterパターンは理解しやすいパターンだと思います。
ただ、僕の場合はこれまでJavaの考え方(静的型付け)で理解していました。
そのため、Rubyのような動的型付けの世界ではいろんな代替手段があるんだなー、と、
改めて感じました。
プログラミングってほんとうにおもしろいですね、みたいな。

*1:ダックタイピングというやつ