ravelll の日記

よしなに

オブジェクト指向設計実践ガイド 第7章

7章はモジュールの話。SOLID 原則の L であるリスコフの置換原則が出てきます。


第7章 モジュールでロールの振る舞いを共有する

  • クラスによる継承はあくまで解法の1つでしかなく、クラスによる継承で解決できる問題には必ず他の解法も存在する

7.1 ロールを理解する

  • 関連のないオブジェクト間で共通の振る舞いをもたせたい場合がある
    • オブジェクトがロール(役割)を担う、という考え方
  • オブジェクトにロールをもたせるとオブジェクト間に依存関係が生じる
    • 振る舞いの共有に発生する依存関係は最小化する
  • Preparer ロールの存在は対応する Preparable モジュールの存在を示唆する
  • ロールを示すコードは1箇所に定義されていつつロールを担いたいどんなオブジェクトからも利用できるようになっているべき
    • モジュールは様々なクラスのオブジェクトが1箇所に定義されたコードを使って共通のロールを担うための完璧な方法
  • どのクラスにどの値を用いるかという知識は Schedule には属さない。属する先は Schedule が名前を確認しているクラス
  • オブジェクトは自分の振る舞いを自分で持つべき
    • オブジェクトAに関心があるとき、オブジェクトAを知りたいがためにオブジェクトBの知識が求められることがあってはならない
  • Schedule にターゲットがスケジュール可能かを聞くのは StringUtils に文字列が空かどうか聞くようなこと
  • モジュールはクラス継承と同じメソッド探索の道筋となるため、コードの書き方やメッセージの解決され方においてクラス継承と同じように振る舞う
  • is-a(クラス継承)と behaves-like-a(モジュールによるコード共有)の違いの重要さ

7.2 継承可能なコードを書く

  • 継承でコードを改善できないか疑うべきアンチパターン
    • オブジェクトが type や category という変数からどんなメッセージを self に送るか決めているパターン
      • 共通のコードは抽象スーパークラスに起き、異なる型をサブクラスとして作る
    • メッセージを受け取るオブジェクトのクラスを確認して送るメッセージを決めてるパターン
      • 受け手になりうるオブジェクトは同じロールを担っている = ダックタイプを見逃している
  • 一部のサブクラスでしか使わないコードはスーパークラスやモジュールに置くべきではない
  • サブクラスはスーパークラスと置換できることを約束する
    • 置換できるのはオブジェクトが期待通りに振る舞うとき、かつサブクラスがスーパークラスのインターフェースに一致するよう"期待される"ときのみ
    • 他のオブジェクトに自身の型を識別させ、自身の扱いや何が期待できるのかを決めさせることはどんなことがあっても許されない
  • リスコフの置換原則
    • システムが正常であるためには派生型は上位型と置換可能でなければならない
  • 継承する側で super を呼び出すのは避けるべき
  • 探索パス上にオブジェクトが多いとメッセージが通過するオブジェクトも増える
  • 階層構造が深いとプログラマーはその頂点と底辺のクラスばかりを理解しがち

7.3 まとめ

  • オブジェクトが共通のロールを担うためには振る舞いを共有する必要があり、それにはモジュールが役立つ
  • モジュールを使うときのコードの書き方は継承を使うときと同じ
    • 継承と同じくテンプレートメソッドパターンを使って include する側のオブジェクトが自然と特化するよう仕向ける
    • フックメソッドを使って include する側に super の送信を強制することを避ける