読者です 読者をやめる 読者になる 読者になる

おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

デザパタを一人でこっそり振り返ろう #3 (Template Method)

この企画は「動的言語においてはデザパタの多くは意味を成さないか違うパターンになる」というよくある俗説?を体感したくて、自分にとってはNative LanguageじゃないSmalltalkで結城先生のJavaデザパタ本 (以下JDP) をなぞってみるという企画でございます。

さて今日のお題からJDPとしては第2部に入りまして「サブクラスにまかせる」というもの。
んで最初がTemplate Methodパターンと。ふむ。

これって要は「親クラスの外部インターフェースを定義している処理を、より詳細にしたメソッドの単純な呼び出しになるよう分割して、その下の具象クラスに実際の処理を隠蔽する」って奴ですよね。

まずは実践

つーことで、JDPの例を、なるべくJDPを見ないようにSmalltalkで書くと*1、まずは AbstractDisplay は:

んで CharDisplay、StringDisplay*2 はこんな感じかな。


あー分かってます分かってます。String>>open と String>>close の実装が愚直すぎることは。コンストラクタで string が与えられたときにサイズ決まるんで、そのときに文字列作って持っておけばいいんだよね。ちょこちょこ試した感じだと、WriteStream 使えばいいのかなって思うんだけど。こんなふうに。

stream := WriteStream with: '+'.
('hogehoge' size) timesRepeat: [stream nextPut: $-].
stream nextPut: $+.
stream contents. "print it."

まあ、それはいいとして。

例によってワークスペースとトランスクリプト出して、

a := CharDisplay with: 'A'.
a display.

b := StringDisplay with: 'hogehoge'.
b display.

ってやって全選択して do it すると、

<<AAAAA>>
+--------+
|hogehoge|
|hogehoge|
|hogehoge|
|hogehoge|
|hogehoge|
+--------+

まぁ、これで一応、やるべきことはできた……けど、なんだか当たり前だのクラッカーだなぁ。デザパタってのはそゆもんっていえばそゆもんだけど。

とゆことで DPSC へ

では DPSC ではどんなことが書いてあるかというと、ちと見てみましょう。

継承って難しいよね実は?

OOPにおいては継承って意外と難しい概念なんで、SELF って言語だと継承をとっぱらっちゃって、代わりに delegate (前回出てきた「委譲」ですな) という概念を導入しちゃってますよとか何とか。

私のへっぽこ英語でさらっと読んだ範囲と自分の経験からいうと、継承が混乱を生むのはその上位下位の関係でなにを抽象化するのかがごちゃごちゃになるからであって*3、「この継承関係はこういうことを抽象化するの!」ときちんとポリシーさえあれば、やっぱり継承は便利だよねと。

で、Template Method はまさにそういう「ポリシーをきちんと決める」例であって。

abstract なメッセージをどう実現するか

えっと、ここは前回からのちょっとした進歩。

私の Smalltalk はまだぴよぴよなので、前回のエントリでは、Java でいう abstract、C++ でいう pure virtual なメッセージの実装方法がわかんなかったわけ。で、コメントだけ書いて定義空っぽにしておいたのね。

でも、ここではまだ実装しないよ、サブクラスの責務だよ、というときには Object>>subclassResponsibility ってメッセージを self に投げとくと、よきに計らってくれるんだそうです。つか、「サブクラスでオーバーライドしないとダメだよん」って実行時エラー出してくれるだけだけど。

でも、実は前回のやり方も間違いってわけじゃなくて、「別に実装しないんだったら、なにもしないでもいいよ」というインターフェースってのもあるでしょうと。そゆ場合には空っぽの実装おいておけばいいよと。
これはあれだよね、Java だったら abstract にしないで stub にしとけばいいとかそういう話だよね。

クラス階層設計の原則

えーと、Auer って人がクラス階層を定義するんだったらこんなふうにしなさいよって指針を出してるそうです。詳しくは DPSC p.359 を読んでください。一応メモ。

  1. クラスは振る舞いによって定義しなさい。状態によって定義してはいけません。
    つまり最初はクラスの構造 (平たくいえば属性;インスタンス変数とか) を考えちゃダメだよと。
  2. 振る舞いを抽象化された状態を使って実装しなさい。
    逆の言い方をすれば、抽象的な振る舞いを実装するのに必要な状態 (属性) だけを考えて、後のことは後でかんがえろということなんだろね。
  3. メッセージのレイヤを認識しなさい。
    つまり親クラスで実現されるべき振る舞いを、サブクラスによって実装されるべきいくつかの小さなメッセージを呼び出す形で考えとけってことかしら。
  4. 状態変数の特定を後回しにしなさい。
    んっとこれは、親クラスでは処理の多くをサブクラスにお任せするわけだから、その処理に必要な状態を親クラスが持つべきではないよねと。サブクラスでそれぞれの処理を実装するときに、じゃあどういう状態 (属性;インスタンス変数) が必要かを考えなさいよと。

まぁ、おっしゃるとおりで。このとおりにやってれば、Template Method って形に自然になるよねーと。


あと Delegate (委譲) について「なにを委譲するの」って説明とか*4、クラス階層のリファクタリングケーススタディとかあって、面白いんだけど省略して。

めんどくさいメソッドを Template Method で実装する方法

というのが書いてあったのでサラッと紹介。詳しくは DPSC 363-364 を。

  1. 最初はまず実装しちゃえ
  2. それをいくつかのステップに細分化しよう
  3. それぞれのステップをメソッド化しよう
  4. そのメソッドを呼ぶように書き換えよう。
  5. メソッドの中にクラス名やリテラルやその他定数になるものがあったら、「定数を取り出すメソッド (constant method)」に分割しよう
  6. 1〜5を繰り返そう。

多分5が一番ミソっぽいよな。ここで constant method をうまく抽出できれば、それを下位のクラスに追い出すことができるわけだから。

あと Smalltalk で Template Method パターンがどう使われてるかってのが延々続くわけだけど、そしてそれがとても面白いのだけど、私が力尽きたので省略。つかみんな DPSC 買って読め。

まとめ

正直 Template Method はあんまり Smalltalk らしさがないんで、前回の Adapter パターンほどワクワクしなかったんだけど、クラス階層設計のポリシーとか Template Method の抽出のしかたとかの丁寧な説明を読んで、DPSC のテキストとしての良さを再認識した気がします。
JDP だとパターンの説明は分かりやすいんだけど、「ほらね、継承使えば便利なことがあるってことがわかったでしょ。でも、どういうふうに継承を使うかってのは決まりはないんだよねー」とか説明が腰が引けてるんだけど、それって DPSC に比べるとズルいと思う。いや、対象読者層が違うといえばそうなんだけど、さ。
単なる公式集じゃなくて、そこからもう一歩上がった「この公式ってどういう考え方で出てくるの?」ということをちゃんと書いてるのが好感度。ここらへん GoF はどうだったっけかなぁ。


さてお次は Factory Method パターンか……これも楽しみですな。しかし、いつ書けるのやら。

*1:最近ムダにgist使うのがお気に入り。

*2:どうでもいいが、Char と String で名前の縮め方のルールが違うのが気になる……。

*3:私の見た酷いコードの例だと、あるクラスの責務が外部仕様の変更によって大きく変わったときに、それを継承によって表現してて (つまり親が I/F なのはいいとして、子が「旧世代のクラス」「新世代のクラス」で兄弟になってる)、新世代のクラスで I/F が変わっていくと、それに合わせて親を変えなきゃいけなくなって、親の I/F が変わるということはそのサブクラスも I/F を合わせなきゃいけないわけだから、もう使われるはずがない旧世代のクラスもせっせこ stub 書かなきゃいけないという最悪のがあった。

*4:これがけっこう面白いのだけど、紹介は機会があれば。