デザパタを一人でこっそり振り返ろう #2 (Adapter)
シリーズ第2回。今回はAdapterパターン*1。
まぁ平たくいっちゃえば「オブジェクト間のインターフェースをコンバートしてやることで、いろんなオブジェクトを統一的に扱うことができるようにしようね」ってもの。まあ「アダプター」だもんね。
インターフェースを使う側を Client、アダプターインターフェースを提供するものを Target、Target の定義するインタフェースを橋渡しするのが Adapter、Adapter から呼ばれて機能を提供するものを Adaptee というようです。
Tailored Pattern
GoF /JDP で説明しているパターンがこれ。
二通りの実装があるよって書いてある。ぐぐれば出てきそうなもんなのでクラス図書いて説明したりはしないけど。
継承方式というのは、Target を抽象クラスにしておいて、Target と Adaptee を多重継承して、Adaptee で定義されているインタフェースを self で使って Adapter がインタフェースを実現するというやり方。
委譲方式というのは、Adapter は具象クラスで、その代わりに内部に Adaptee のインスタンスを持って、そのインスタンスにメッセージを送ることでインターフェースを実現する方法。Adaptee のインスタンスに「この処理お願いっ」ってするから「委譲」っていうんだね。
ということは、「Adapter が、Target に提供すべきインターフェースと、Adaptee の提供するインターフェースをがっちり知っていなければならない」ですよね。
ということで、DPSC ではこれを「Tailored Adapter」と呼んでます。「仕立てアダプター」っつか「オーダーメイドアダプター」とでも言うのか。
んでDPSCでは「Smalltalkには多重継承ないから、委譲でしかできないんだよね」ってことで GoF の例を実装してるけど、せっかくだから JDP の例をやってみませうか。
でも JDP の例ってあんまよくなくて、モノが一個しかないのになんでインターフェースを併せてあげなきゃいけないの? って思うよね。ココらへんはLineとTextという違うインタフェースを持つものを統一的に扱いたいから……っていうGoF/DPSCの例のほうが分かりやすいとは思うけど、動かして結果が見やすいからこういう例なのかな?
まあさておき、ぼくが Smalltalk で愚直に実装するとなるとこうかな。
Banner クラスはこんなんで、
Print クラスはすごくシンプル。
PrintBanner クラスはこんな感じかなぁ。
トランスクリプトとワークスペース開いてワークスペースに以下のコードを書いて。
testBanner := Banner has: 'Hello'.
testBanner showWithAsters.
testBanner showWithParens.
Transcript cr.
test := PrintBanner with: testBanner.
test printStrong.
test printWeak.
うりゃっと全選択して do it すればトランスクリプトに、
*Hello* (Hello) *Hello* (Hello)
されますねと。
Traits 使ってみよう
Traits というのは Ruby のモジュール mix-in に近いけど、名前がぶつかったときにエイリアスで逃したりできる、もっと緩い感じのモノ、という理解でいいのかな。
Matz さんが Ruby にも入れたいなーと言っているらしいところを見ると、mix-in とは違ったメリットがあるんでしょう。多分。
Squeak の派生である処理系 Pharo と、Squeak 3.9*2 から入りました。
これを使えば継承方式で実装できるのかなーなんて思った。
ということでさささっと書いてみたっす。
まずは Print を Trait として定義するわけだけど、Smalltalk 的には Trait には T という Prefix を付けるようなので、名前を変えまして。
んで、PrintBanner の定義はこんなふうになるのかな。名前は継承版と被らないように一応変えておいた。
使い方は委譲方式とちょい変わって、
test := PrintBanner2 has: 'Hello'.
test printStrong.
test printWeak.
で、ちゃんと実行できました。ぱちぱち。
そもそも Tailored Adapter っているの?
ぼくがこの説明を JDP で読んで、最初に思ったのは、
なにもこんな大げさなコトやらなくても、
Banner>>printStrong self showWithAsters. Banner>>printWeak self showWithParens.でよくね?
ということだった。
DPSC にもその点については書いてあって、「C++ だったらリコンパイルがいるからこういうやり方はちょっとないけど、Smalltalk だったらやってダメな法はないよねー」って書いてあった。やっぱそうか、そうなんか。
多分 ruby でもこんな感じに書けるんじゃないかな。
class Banner def printStrong self.showWithAsters end end
Message-Based Pluggable Adapter
さて Tailored Adapter は「呼ぶ方のも呼ばれる方のもインターフェースが明らか」という仮定をおいていた。でも、この仮定が崩れたとしたら?
つまり、設計ができた「あとから」新しい Adaptee ができるとしたら?
例えば MVC パターンでウィジェットが表示用にモデルから値を取り出すときなんかを考えると、ビューのウィジェットが Client でモデルが Adaptee になって、クライアント側は value メッセージで表示用の値をとって value: メッセージでセットするとかなる。ところがモデルになるクラスって自前で作るとは限らなくて他所からの流用だったりライブラリだったりするわけで、そうなるとそれごとにAdapter書かなきゃいけない。そういう種類が違うモデルが出現するたびにこんなに大騒ぎするのめんどくさいですよねえ。
めんどくさいのもそうなんだけど、クラスってのはオブジェクトの「雛形」なんであって、Adaptee の数だけ雛形が存在するのって、ちょっとなんだかなぁ、って思いません? ふつー思いますよね。
そこで Smalltalk で活躍するのがこの Message-Based Pluggable Adapter なのだ。
要はね、Adaptee が新たに登場するたびに Adapter クラスを作るのって大変じゃないですか。
そこで、インスタンスに対して動的に動作を突っ込める Smalltalk の機能を活用して、「Adapter クラスのこのインスタンスはこの Adaptee 用」ってできれば嬉しいじゃないってこと。だとぼくは理解してる。
例えばさっき例に出した MVC の話で言うと、ビューがモデルから値を取る value ってメッセージと、逆にモデルに値を設定する value: ってメッセージが考えられるじゃないですか。で、こいつらの動きを、インスタンスごとに定義できたらクラスが爆発しないでいいよねと。
DPSC の写経をしてもしょうがないので大雑把な構造を書くと、
- Adapter クラスはコンストラクタで Adaptee のインスタンスを貰っておく。これは以上による Tailored Adaptor と同じですね。
- Smalltalk には "perform:" ってメッセージがあって、以下の二つは等価なんです。
anObject #hogehoge = anObject perform: #hogehoge
- ここで重要なのは、#hogehoge が変数に代入されたものであっても構わないということ。だから例えば getSelector という変数に束縛されているとしたら、以下のように書けるということ。
anObject perform: getSelector
- ということは selector メッセージを使えば、一般的な Adapter というクラスのインスタンスオブジェクトに対して「value に対するメッセージは何?」という設定ができるってことになるわけです。
例を挙げましょう。細かいクラス定義は省略するとして、あ、anObject と printStrongSelector は MessageAdapter のインスタンス変数ね。
と定義しておけば、
adapter := MessageAdapter on: (Banner has: 'Hello'). adapter setPrintStrong: #showWithAsters. adapter printStrong. " => *Hello*"
と実行できますよと。おお、すげー。
さらに文字列をシンボルに変えるメッセージ asSymbol を使えば、シンボル一個渡すだけで、getter / setter ど同時に定義できたりする。
この仕組みを使うことで、クラス数の爆発を抑えつつ、MVC なんかでの M と V の結合を柔軟に変動させることができる。うーん、これぞプログラミングの醍醐味という奴ではないかい?
この仕組みはめちゃんこクールだと思う。詳しくは DPSC p.114-115 を読むとよいですよ。
Parameterized Adapter
Message-Based Pluggable Adapter はとてもクールなんだけど、インタフェースがパラメータを要求するとき、それを渡すすべがないという問題? がある。
ということでそんな場合に使うのが Parameterized Adapter。
すごい平たくいってしまうと「getter / setter にブロックを渡してしまう」って Adaper。GoF や JDP における Adapter パターンとははるか遠くに来てしまいましたが、モダンなプログラミング言語なら似たようなことはできると思う。
あまりまとまってないまとめ。
- GoF や JPD で紹介されている Tailored Adapter は Smalltalk (あるいは他のモダンな言語) でも実装できるけど、追加すべきメソッドが静的に決まるのであれば、既存クラスを直接いぢっちゃても (動的言語の場合) 問題ないので、正直誰得。
- その点、インスタンスごとにあるオブジェクトのメッセージをどうディスパッチするかを指定できる Message-Based Pluggable Adapter はすごい強力。
- ということで、Pluggable Adapter は VisualWorks でもりもり使われてる、そうな。
ここで紹介したことがすべてではないので、動的言語においてデザインパターンっってどうあるべきなのかってことに興味がある人は、ぜひ
Design Patterns Smalltalk Companion, The (Software Patterns Series)
- 作者: Sherman Brown, Kyle Woolf, Bobby Alpert
- 出版社/メーカー: Addison-Wesley Professional
- 発売日: 1998/02/10
- メディア: ペーパーバック
- 購入: 1人 クリック: 23回
- この商品を含むブログ (9件) を見る
さて次はなんだっけ。Template Method だっけ。
DPSC 的にはどんな切り口なのか楽しみですね。