Adapter
Rubyによるデザインパターン生活。若干飽きてきたけど続けます。
本日はAdapterパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
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
- 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:ダックタイピングというやつ