お正月休みに一年ほど積読だったオブジェクト指向設計実践ガイドを読んだのでメモ。
第2章 単一責任クラスを設計する
単一責任クラス・メソッドがもたらす恩恵
隠蔽されていた性質を明らかにする
クラス内のメソッドをほかのクラスに再編する意図がないときでも、それぞれのメソッドが単一の目的を果たすようにすることによって、クラスが行うことが全体より明確になる
コメントする必要がない
メソッド内のコードにコメントが必要ならば、そのコードを別のメソッドに抽出しましょう。その新しいメソッドの名前が当初の目的を果たします。
再利用を促進する
小さなメソッドは、アプリケーションにとって健康的なコードの書き方を促進する。
ほかのクラスへの移動がかんたん
設計のための情報がもっと増え、変更をしようと決めたとき、小さなメソッドはかんたんに動かせる
第3章 依存関係を管理する
依存方向の選択
自身より変更されないものに依存しなさい
- あるクラスは、ほかのくらすよりも要件が変わりやすい
- 具象クラスは、抽象クラスよりも変わる可能性が高い
- 多くのところから依存されたクラスを変更すると、広範囲に影響が及ぶ
依存されている数 | 要件が変わる可能性 | ||
---|---|---|---|
↑多い | **抽象領域A:**変更は起こりにくいが、変更が起こると広範囲に影響を及ぼす | **危険領域D:**変更は起こる。そして、変更はそこに依存するものに伝わっていく | ↑高い |
↓少ない | **中立領域B:**変更は起こりにくく、副作用もわずかしかない | **中立領域C:**変更は起こりやすいが、副作用はわずかしかない | ↓低い |
第4章 柔軟なインターフェースをつくる
明示的なインターフェース
- 明示的にパブリックインターフェースだと特定できる
- 「どのように」よりも「何を」になっている
- 名前は、考えられる限り、変わり得ないものである
- オプション引数として、ハッシュをとる
第5章 ダックタイピングでコストを削減する
具象的なコードは、理解はかんたんですが、拡張にはコストが伴います。抽象的なコードは、最初のわかりにくさは増すかもしれませんが、一度理解してしまえば、はるかに変更しやすいのです。
動的型付け恐れるプログラマーは、コード内でオブジェクトのクラスを検査する傾向にあります。この検査こそ、まさに動的型付けの力を削ぐものであり、ダックタイプの利用を不可能にしているのです。
- 静的型付けの主張
コンパイラは不慮の型エラーから本当に救って「くれる」
コンパイラの助けなしでは、これらの型エラーは「起こる」
- 動的型付けの主張
変数を新しい型にキャストできる脆さがある
型エラーを防ぐのはプログラマーの力
コードの良さは結局テストの良さ
第6章 継承によって振る舞いを獲得する
抽象クラス
抽象クラスはサブクラスがつくられるために存在します。これが唯一の目的です。抽象クラスは、サブクラス間で共有される振る舞いの共通の格納場所を提供します。
明示的にクラスを抽象概念として宣言する構文をもつものもあります。たとえば、Javaには、abstractキーワードがあります。Javaのコンパイラ自体が、このキーワードが適用されたクラスのインスタンスが作成されることを防ぎます。Rubyは、他者を信頼する性質から、そのようなキーワードは備えておらず、制約を加えることはありません。ほかのプログラマがインスタンスをつくること防げるのは、良識だけです。
抽象スーパークラスは、テンプレートメソッドパターンを使うことで、その継承者に専門的に特化するように促します。
フックメソッドを使うことで、superの送信を強制せずとも継承者がスーパークラスに特化を提供できるようにします。
フックメソッドによって、抽象的なアルゴリズムを知らなくともサブクラスは専門性を加えられるようになります。
サブクラスがsuperを送る必要性が取り除かれるので、階層構造の各層間の結合度が低減し、変更にも強くなります。
第7章 モジュールでロールの振る舞いを共有する
オブジェクトが共通のロールを担うためには、振る舞いを共有する必要があり、そのためにはRubyのモジュールが役立ちます。
モジュールを使うときのコードの書き方は、テンプレートメソッドパターンを使い、includeする側のオブジェクトが自然と特化を行うように仕向けるべきです。
includeする側にsuperの送信(アルゴリズムを知ること)を強制することを避けるためにも、フックメソッドを実装しましょう。
派生型は上位型と置換可能であるべき
第8章 コンポジションでオブジェクトを組み合わせる
コンポジションか継承かの選択
直面した問題がコンポジションによって解決できるものであれば、まずはコンポジションで解決することを優先するべきです。継承のほうがより良い解決法であるとはっきり言い切れない時は、コンポジションを使いましょう。
- 継承
is-a関係に継承を使う
継承が最も適しているのは、過去のコードの大部分を使いつつ、新たなコードの追加が比較的少量のときに、既存のクラスに機能を追加する場合です
- コンポジション
has-a関係にコンポジションを使う
振る舞いが、それを構成するパーツの総和を上回るのなら、コンポジションを使いましょう
- ダックタイプ
behaves-like-a関係にダックタイプを使う
継承の利点
- 「合理的」
階層の高さがてこの役割を果たし、その影響を何倍にも高めるから。メソッドへ加えられた変更は、継承ツリーを波及していきます。
- 「利用性が高い」
階層構造は、拡張には開いており、修正には閉じています。新たなサブクラスを既存の階層構造へ追加する時、既存のコードへの変更はまったく必要ありません。
- 「模範的」
正しく書かれた階層構造はかんたんに拡張できます。階層構造は、抽象を明らかにし、すべての新しいサブクラスは、少しの具象的な違いを差し込みます。その階層構造における既存のパターンは簡単に倣うことができるものであり、そのパターンを踏襲するのは、新しいサブクラスをつくる責任を負ったプラグラマーの自然の選択です。
継承のコスト
- 「合理的」
間違ってモデル化された階層構造の頂点近くの変更にかかる、莫大なコスト。
- 「利用性が高い」
サブクラスが複数の型を混合したものの表現であるときの、振る舞いの追加の不可能さ
- 「模範的」
不適切にモデル化された階層構造を初級プログラマーが拡張しようとした結果もたらす、既存コードの複製、クラス名に対する依存
コンポジションの利点
コンポジションに参加するオブジェクトは小さく、構造的に独立しており、そして適切にインタフェースを持ちます。そのため、それらは、抜き差し可能で、入れ替え可能なコンポーネントへと円滑に移行できます。
適切にコンポーズされたオブジェクトは「利用性が高く」想定していなかった新たなコンテキストでもかんたんに利用できる
コンポジションのコスト
コンポーズされたオブジェクトは多くのパーツに依存します。それぞれの部品は小さく、かんたんに理解できるものであったとしても、組み合わせられた全体の動作は、理解しやすいとはいえないでしょう。個々の部品はすべて「見通しが良い」ものであったとしても、全体はそうでないかもしれない。