オブジェクト指向設計実践ガイド 第6章
6章は継承の話。既知の内容も多かったけど、フックメソッドのテクニックは知っておらず、なるほど〜という感じだった。
徐々にサンプルコードのコンテキストが厚くなってきたので写経しつつ読み進めた。
それにしてもこの本、読み進めているとたびたび唐突に自転車が猛烈プッシュされてきて、著者の抑えきれない自転車愛が伝わってくるのが面白い(知識があるだけなのかもしれないけれど)。まさかリカンベントまで出てくるとは。
6章のホット・バイシクル・センテンシーズはこちらです。
「ハンドルバーのテープはそこまで重要でなさそうですが、 実際には(タイヤやチェーンと)同じくらいに必要とされます。 誇りを持つサイクリストの中に、横れたり破けたりしたバーテープを許す人はいません」
第6章 継承によって振る舞いを獲得する
6.1 クラスによる継承を理解する
- 継承:メッセージの自動委譲の仕組み
- あるオブジェクトが受け取ったメッセージに応答できないとき他のオブジェクトにそのメッセージを委譲する
6.2 継承を使うべき箇所を識別する
- オブジェクトの属性による分岐
- ダックタイピングが解決する問題と同じパターン
- 「自身の分類を保持する属性を確認し、自身に送るメッセージを決定する」
- type, category という変数名はその根底にあるパターンへの気づきを促している
- 継承は共通の振る舞いを持つもののいくつかの面においては異なるという強く関連した型の問題を解決する
- サブクラスはスーパークラスの全てであり、スーパークラスを上回るものである
- メッセージがスーパークラスの階層構造を遡って委譲されていく
6.3 継承を不適切に適用する
6.4 抽象を見つける
- 継承が効果を発揮するために必要な2つのルール
- モデル化しているオブジェクトが “一般 - 特殊” の関係を持っていること
- 正しいコーディングテクニックを使っていること
- Ruby は他者を信頼する性質から Java における abstract 等ののキーワードを持たず、制約を加えることもない
- 抽象クラスはサブクラスが作られるためだけに存在する
- 抽象クラスがサブクラス間で共有される振る舞いの格納場所を提供する
- サブクラスがそれぞれに特化した振る舞いを用意する
- サブクラスを1つだけもつ抽象クラスを作ることはほぼまったく意味がない
- 階層構造を進める決断は、正しい設計のための情報が未だ無いかもしれない、というリスクを受け入れること
- 今2種類のカテゴリは3種類になる見込みがあるなら階層構造を作ったほうがいいかもしれない
- 2種類から増えないならば階層構造を作らないほうが低コストかもしれない
- 可能なら判断を待つ。しかし最善だと思えるならば進む
- 既存のコードはスーパークラスに昇格させるよりサブクラスに降格させる方が簡単
- 抽象クラスを作るときは一度全体を降格させてから必要な分の実装を昇格させるのがよい
- リファクタリングやその他の戦略を決める際は「もし間違っているなら何が起こるか」を質問するのが有用
- 信頼できない階層構造は、それと関わるオブジェクトに階層構造の癖を知るよう強要する
- そのときよく使われる手段が、オブジェクトのクラスの明示的な確認
- 設計者の決断に常に伴う2つのコスト
- 実装コスト
- 実装が間違いと分かったときの変更コスト
- Bicycle クラスは初期値をメソッドで包み、それをオーバーライドすることで何かに特化できる機会をサブクラスに与えている
- スーパークラス無いで基本構造を定義し、サブクラス固有の情報を得るためにメッセージを送る
- このようなテクニックをテンプレートメソッドパターンという
- テンプレートメソッドをサブクラスで特化させる必要があると明示的に示すには、スーパークラスのメソッドで未実装と分かるようなエラーを出す
6.5 スーパークラスとサブクラス間の結合度を管理する
- サブクラスは自身を特化する振る舞いについて知っているのは良いが、super で抽象スーパークラスとの関わり方まで知るよう強制するのは良くない
- 全てのサブクラスが正確に同じ箇所で super する必要があるのは、新たにサブクラスを書くときにエラーを作る可能性を高める
- サブクラスが super を送ることは、サブクラスはスーパークラスの処理を知っていて、その知識に依存しているということ
- super を送ることをサブクラスに求めるのではなく、フックメッセージを用意してスーパークラスから情報を取得する
- サブクラスは自身について「何を」特化する必要があるかについては責任を負うが、「いつ」特化するかについては責任がなくなる
まとめ
オブジェクト指向設計実践ガイド 第5章
5章はダックタイピングの話。Ruby の特徴としてよく語られますね。
勘所がつかめると設計が洗練されるだけでなく OSS プロダクトのコードリーディングが捗って良さそうだな〜と思いました。
動的/静的型付け言語の話はなかなか強い口調で語られていて、読んでいて少しヒヤヒヤするところがあった。
5章 ダックタイピングでコストを削減する
- ダックタイプ:どの特定のクラスにも結びつかない(クラスをまたぐ)パブリックインターフェース
- 「アヒルのように鳴き、アヒルのように歩くならば、それはアヒルである」
5.1 ダックタイピングを理解する
- Ruby ではパブリックインターフェースを信頼を元にオブジェクトの振る舞いを期待する
- 「あるオブジェクトの型を知っている」ことは「オブジェクトが応答できるメッセージを知っている」ということ
- あるクラス A のオブジェクトと相互作用するオブジェクトに必要なことは「相手が A のインターフェースを実装していると信頼する」こと
- オブジェクトの使い手はそのクラスを気にする必要がなく、気にするべきでない
- 重要なことはオブジェクトが「何であるか」でなく「何をするか」
- ダックタイプのパブリックインターフェースは契約を表す
- Trip#prepare は Mechanic クラスには依存していないが「prepare_bicycles に応答できるオブジェクトを受け取る」ことに依存している
- ダックタイプ化は少しの理解力と引き換えに拡張の容易さを提供する
- 具象的なコードは理解易・拡張難、抽象的なコードは理解難・拡張易
- オブジェクト指向におけるポリモーフィズム:種々のオブジェクトが同じメッセージに応答できる能力
5.2 ダックを信頼するコードを書く
- クラスによって分岐する case 文は未特定のダックの存在を示唆する
- 共同作業するオブジェクトについての知識を持ちすぎていて、信頼が欠けている
- ダックタイプを実装するクラスは振る舞いもいくらか共有する必要がある
- モジュールによってロールを共有する方法に続く
- ダックタイプは時と場合を考えて使う
- ダックタイプを見つけて実装してもアプリケーション全体のコストが下がらない場合もある
5.3 ダックタイピングへの恐れを克服する
- コンパイラは不慮の型エラーからプログラマーを救うことはできない
- 変数を新しい型にキャストできる全ての言語には脆さがある
- 静的型付けによって安全になるという考えは幻想である
- 現実の世界で、コンパイラで防げる実行時の型エラーはほぼ確実に起こらない
- コンパイラの助けがなければ型エラーは起こる、という説に対して
- 動的型付け言語は、高コスト低価値なコンパイル時の型検査と、コンパイル/make サイクルを除くことで得られる莫大な効率性とを交換してくれる
- :fire:
5.4 まとめ
- ダックタイピングはパブリックインターフェースを特定のクラスから切り離し、「何であるか」ではなく「何をするか」によって定義される仮想の型を作る
第110回 PHP 勉強会に行ってきた + LT してきた
久しぶりの PHP 勉強会。最後に行ったのは第96回で、調べてみたらもう1年以上も前のことだった。当時に比べれば随分と PHP に詳しくなったと思う。
LT は最近踏んだバグについて話してきた。そこそこ盛り上がって安心しました。
勉強会全体としては、 @ex_takezawa さんの DDD の話で過去に自分がチームに DDD 導入しようとして爆死した思い出が蘇ったり、@kuwahara_jsng さんから自分に知見があまりない CodeIgniter4 の話が聞けたり、飛び入り参加で @tomzoh さんがめっちゃいい話されてたりで、短時間ながらとても楽しく得られる物があった会でした。
来月も何かいいネタがあれば LT 応募しようと思います、お疲れ様でした!
PHP最高!!! #phpstudy pic.twitter.com/es3dxfoG72
— 谷口 (@ravelll) 2017年1月31日