今日は前回の講義の続きをします。
前回は小さなメソッドの利点を 説明しました。
今日はいくつかの例をお見せします。
まずは前回の復習をしましょう。
メッセージを送ることは 分岐をもたらすことがあります。
つまり、実際にどの実装が使われるか
表面上明らかでない分岐といえます。
そして Pharo がメッセージのレシーバー に応じて、どの分岐を辿るか選択します。
クラス階層が分岐の場合分けを定義します。 クラスが多ければ場合分けも増えます。
サブクラスを作ることで 簡単に場合を追加できます。
サブクラスはスーパークラスのコードを 上書きしたり修正することができます。
そして、メッセージを送ることは
サブクラスに振る舞いを変える機会を 与えるということです。
今日は、テンプレートメソッドと呼ばれる デザインパターンを見てみましょう。
デザインパターンの本に載っています。
テンプレートメソッドとは
あるアルゴリズムの 全体的な振る舞いを定義して、
内部にフックを用意したものです。
そしてフックはサブクラスによって 再定義することができます。
これがアルゴリズムです。
このアルゴリズムは何かをするわけですが 正確に何をするのかはわかりません。
そして hookMethod1 で何かをします。
そしてまた何かをやって
そして hookMethod2 で何かをします。
hoodMethod1 と hookMethod2 は どちらも完結したメソッドで
サブクラスで再定義されます。
hookMethod1 と 2 のそれぞれについて 2 つの可能性があります。
メソッドがデフォルトの振る舞いを
持っているかどうかです。
ここでは、 hookMethod1 には デフォルトの振る舞いがあるとしましょう。
つまり、サブクラスが何も定義しなければ
hookMethod1 はデフォルトの振る舞いで 機能するということです。
一方、hookMethod2 は
デフォルトの振る舞いを 持たないとします。
サブクラスが振る舞いを 定義しなければなりません。
クラスの設計者が デフォルトの振る舞いを
定義するかどうかを選択します。
printString を 1 つ目の例にします。
オブジェクトに printString メッセージを送ると
そのオブジェクトを表現した 文字列が得られるというものです。
ここにディレイ（Delay）があります。
10 秒のディレイを作りました。
このディレイに printString メッセージを送ると
「a Delay」に加えて括弧の中に 何ミリ秒かを示す値が表示されます。
printString メソッドは Object クラスで このように定義されています。
printStringLimitedTo: メッセージを 送ります。
printStringLimitedTo: の実装で
そのオブジェクトを表現する文字列を
得ています。
もしその文字列が長すぎる場合には
適切な長さで切り捨てて
最後に「...etc...」と付け加えて
この文字列は最後まで表示されていない ことを表します。
ここで重要なことは
printStringLimitedTo: は self に printOn: メッセージを送ることです。
このメソッドが
サブクラスで再定義されます。 （再定義されないこともあります）
Node や Apple の printString で
何が得られるか見てみると
Node new は 「a Node」を返します。
これが Node クラスの printString です。
そしてこれが Apple クラスの printString です。
この振る舞い デフォルトの振る舞いは
Object クラスで定義されています。
つまり、あらゆるオブジェクトの printString のデフォルトの振る舞いが
これです。
クラス名を取得します。
この場合は Node と Apple です。
そしてクラス名が母音で始まっていれば
前に「an」を加えて
子音で始まっていれば 「a」にします。
こうすることで「a Node」や 「an Apple」が得られます。
これがデフォルトの振る舞いです。
このデフォルトの振る舞いは
printOn: を再定義することで 変更することができます。
ディレイの場合には
Delay の printString は
デフォルトの printOn: のもので 始まります。
つまり「a Delay」の部分です。
そしてそれに続いて、括弧の中に ミリ秒の値を加えています。
printOn: メソッドがやっていることを そのまま説明すると
まず最初にスーパークラスの デフォルトの printOn: をします。
続いて、括弧を開いて
設定されたミリ秒の値を表示して
そして括弧を閉じます。
このように再定義されています。
Delay クラスは Object クラスの printOn: メソッドを
修正しています。
あるいは、振る舞いを完全に再定義 することもできます。
例えば、真偽値がそうです。
false を表示すると
「false」が得られます。
「a false」ではなく
単に「false」です。
こうするために、 printOn: では
単に文字列「false」を ストリームに送っています。
完全に上書きで再定義しています。
上書きでの再定義のもう 1 つの例は インターバルです。
インターバルは最小値から最大値までの
間にある値の集合です。
1 から 100 までのインターバルは 「(1 to: 100)」と表示されます。
インターバル 1 to: 100 by: 3 も
1、4、と続きますが
by 付きで同じように表示されます。
これがどのように 実装されているかというと
Interval クラスが printOn: メソッドを再定義して
ストリームに異なるメッセージを 送っています。
まず括弧を開きます。
括弧はここと、ここにあります。
そしてインターバルの 最初の値を表示します。
ここでは、この「1」と ここでは、この「1」です。
そして「to:」を書きます。
そして最後の値を書きます。 この「100」と
この「100」です。
そして「by」が必要な場合、つまり デフォルトの 1 以外の場合には
それを書きます。
そして最後に 括弧を閉じます。
以上のように、printString は
デザインパターンの テンプレートメソッドを使うことで
それぞれのクラスが自分自身の 文字列表現を実装できるようになっています。
もう 1 つの例を見てみましょう。 オブジェクトのコピーです。
copy を使って
あるオブジェクトから コピーを作ることができます。
オブジェクトのコピーは複雑です。
いくつかのやり方がありますが
インスタンスのコピーが どうなるべきかを
それぞれのクラスが決めます。
テンプレートメソッドを使った シンプルな解決策が
copy と postCopy を使う方法です。
copy がテンプレートメソッドで postCopy がフックです。
copy メソッドは Object クラスにあります。
コメントを読んでください。
このメソッドでは shallowCopy メッセージを
self に送って
続いて 結果に postCopy を送ります。
shallowCopy は
元のオブジェクトのインスタンス変数の 内容を共有する
新しいオブジェクトを生成します。
2 つのオブジェクトは 同一のインスタンス変数を持っています。
一方のオブジェクトのインスタンス変数が 指すオブジェクトでの変更操作は
もう一方のオブジェクトの インスタンス変数からも見えます。
これが shallowCopy の デフォルトの振る舞いです。
postCopy の定義次第で
変数が同じオブジェクトを共有するかどうか が決まります。
postCopy が何もしない場合には 全ての変数の参照先は共有されます。
それぞれのクラスが ある変数は共有して、また別の変数は
共有しないということを postCopy で決めます。
postCopy のデフォルト動作では 全て共有されます。
デフォルトの postCopy は そのオブジェクトをそのまま返します。
しかしそれ以外のものもあります。
例えば、Bag クラスです。
Bag は集合の一種ですが
postCopy メソッドはその中身を コピーします。
Bag には インスタンス変数 contents がありますが
Bag クラスの設計者は
Bag のコピーを作る時には
インスタンス変数 contents を 共有せずに
別々のオブジェクトを指すようにしました。 双方の contents は
同じ初期値を持っていますが 一方のみを変更することができます。
つまり
postCopy は 新しく作られたコピーに送られます。
コピーのインスタンス変数は 元のオブジェクトと参照を共有しています。
共有したくない場合には
新しく作ります。
ここに 変数 contents がありますが
ここに元のオブジェクトの コピーを入れます。
こうして共有しないようにします。
それぞれの Bag ごとに contents の参照先を持っています。
Dictionary（辞書）ではより複雑です。
辞書はキーと値のペアの集合
言い換えると、関連のコレクションです。
辞書のコピーを作成する時には
関連のコレクションをコピーするだけでなく
それぞれの関連も
それぞれの辞書で別のものに する必要があります。
そうすることで一方の辞書を書き変えても もう一方には影響しないようにします。
そのためには テーブルをコピーするだけでなく
その中の各ペアもコピーします。
これが辞書のコピーで 行われていることです。
まとめるとデザインパターンの テンプレートメソッドはとてもよく使われます。
良い設計の目印です。
良いオブジェクト設計をすれば たくさんのテンプレートメソッドができます。
それが自然で良い設計です。
テンプレートメソッドを使うことを 躊躇することはありません。
この技術はサブクラスが 振る舞いを定義することで
スーパークラスが定義した振る舞いを 部分的に変更することを可能にするものです。