タイトル未定
前回までのまとめ
そのコンポーネントが目的とする処理を実現するには、正しい順番で必要なメソッドを呼び出していく必要があり、呼び出し順序はオブジェクトの木で規定される。オブジェクトの木の各オブジェクトは重複することがなく、各オブジェクトは自分の子だけに依存する。
ソフトが実現したい最終的な目的を果たすために、汎用のコンポーネントをオブジェクトの木に組み込むことになる。依存性を切断された汎用目的のコンポーネントに依存性を注入し、特化したものをオブジェクトの木から利用する。
汎用目的のコンポーネントにも自分がなすべき処理があり、その処理を実行するために自分のオブジェクトの木を持っている。
今後の話
その処理が一度に全てが実行される類のものなら、状態を持つ必要はないが、GUIなどを用いて、フィードバックを行いながら複数回に分けて実行される可能性がある場合、オブジェクトは状態を持ち、状態を書き換えながら実行される必要がある。
状態の書き換えは問題をやたらと複雑にするので出来る限りやらないで済ませるべき。
一般に時間のかかる処理は、進捗状況をその都度報告してもらい、途中でキャンセルできる必要があり、マルチスレッドで実行を途中で止める仕組みか、複数回に分けて少しずつ実行する仕組みを入れなくてはいけない。マルチスレッド実行を途中で止める仕組みの方が状態を持たなくて済むので良い。
途中まで実行された状態を見せる必要があるのは、移り変わる何かのシミュレーションであったり、アニメーションであったり、移り変わることが前提の処理になる。移り変わる処理では状態をもたせるほかない。
なので、「時間のかかる処理」「移り変わる処理」では状態をもたせる必要がある。他では状態をもたせる必要はない。いままで状態を持っていたものを一つ一つ切っていく必要がある。
移り変わる中でユーザーからの入力を受け付け、移り変わり方が変化することもある。単に時間がかかるだけの処理のように、移り変わり方が一通りであればコルーチンのような言語機能を使った単純な記述も可能であるが、変化する場合はもっとややこしく、オブジェクトの木のどこから処理が開始されるかわからなくなる。GUIを処理すると、このややこしさとと付き合うことになる。他にもアニメーションはコルーチンで書きうるけれども入力に反応するゲームは書けない。
なので処理の開始地点によって分類できる。
単純処理:オブジェクトの木のルートから始まる。
一方向の処理:コルーチンの中断地点またはスレッドの今実行している箇所から始まる(始まる?)
分岐する処理:登録しておいた場所のどこかから始まる。
その話を流れに沿って書いていく。
オブジェクトの木を行き来するもの
前回オブジェクトの木を導入したことで、オブジェクトの依存関係は制約を受けることになった。オブジェクトの木は重複できないので、そのルールに厳密に従えば、2つのオブジェクトから使われるようなオブジェクトは存在できない。
それは全オブジェクトに適用されるわけではない。データはやり取りしなければならないので最低限2つのオブジェクトから使われる。オブジェクトの木は処理の順序を規定するもので、データは処理をしないのでオブジェクトの木に含まれない。逆に言うとデータは処理をしてはいけない。
処理は一般にこのような形になる。
OutputDataType 処理(InputDataType1 input1, InputDataType2 input2){...}
注:inputを最小にする、とか暗黙の第一引数、といったことについて、どこかに説明をねじ込まなければいけない。
InputDataTypeやOutputDataTypeはオブジェクトの木に含まれないので、処理をしてはいけない。しかしデータタイプと言っても、例えばコレクションであればコレクションを扱う各種のメソッドを持っている。そういった汎用の処理は行える必要がある。
定義する。ソフトの目的のための処理を行うオブジェクトはオブジェクトの木に所属しオブジェクトの木の依存関係のルールに従う。データはソフトの目的に依存した処理を行わず、汎用の処理だけを行う。データはオブジェクトの木のオブジェクトに依存しない。
オブジェクトの木の各種メソッドがデータをやりとりすることで処理を行うことになる。ところで、メソッドの基本ルールとして、引数は書き換えないというものがある。戻り値も書き換える必要はない(普通は書き換えようという発想にならないので注意する必要もない)。
なのでデータを書き換える必要はない。定義を付け加える。データは書き換えない。よって書き換えるようなデータはオブジェクトの木に所属させなければならない。
データは書き換えない
JavaやC#ではコレクションのインターフェースにAddメソッドのような、コレクションの内容を変化させて書き換えるメソッドがある。配列も書き換えが可能である。
その昔、配列の変換にも苦労した時代があった。OldType型の配列からNewType型の配列に変換する場合、このようなコードを書いた。
NewType[] Hoge(OldType[] oldArray) { NewType[] newArray = NewType[oldArray.Length]; for(int i = 0; i < newArray.Length; ++i) { newArray[i] = ToNewType(oldArray[i]); } return newArray; }
なので配列は書き換え可能である必要があった。
今はこのように書かれる。
oldArray.Select(old => ToNewType(old)).ToArray();
ラムダ式を使ったコレクションライブラリがあれば、通常の使用では書き換えメソッドを呼ぶ必要はない。
このように、初期化がうまくできないので、あとから初期化するためにパブリックなセッターなどの書き換えメソッドが必要になることがあったが、ラムダ式のおかげで必要なくなった。標準でついてくるAddメソッドやパブリックセッターなどは無用の長物だ。
なのでデータクラスを自作する場合は書き換えメソッドを最初から用意せず、昔からある古い設計のコレクションを使う場合は、書き換えメソッドは使わないようにするのが良い。