オブジェクト指向設計実践ガイド5章を読んだ感想

この章は、ダックタイピングの話。これまでの章もそうだったけど、いかに抽象化して、再利用可能にして、変更コストを下げるかというのがたくさん述べられてた。やはりそれだけ重要ってことですね。ダックタイピングは、動的型付け言語の抽象化を促してくれるやり方の一つで、ダックタイピングを使って抽象化しましょうという感じの内容。めちゃめちゃ勉強になった。ここまで読み進めて、抽象化、再利用、インターフェース、変更コストなど、オブジェクト指向設計で重要と思われるキーワードを何度も目にして思ったことなんだけど、この概念たちは、プログラミングの世界に限らず、世の中一般的なことで考えても、当てはまる気がした。仕事とか、日常生活の中で起こる何かしらの具体的な行動、成功体験を、自分なりに解釈して、抽象度をあげて整理して、それをまた別の行動にも応用、再利用するみたいなことは、割と一般的に考えてる人は多いと思う。プログラミングの世界でも、具体的なコードがいくつかあって、それらの共通する部分を切り出して、抽象的に考えて、コードに落とし込むことによって、応用の効く、再利用可能なコードが出来あがるんじゃないかなと思ったりした。

イントロ

  • 次の2つの考えを組み合わせるのが強力な設計テクニック
    • メッセージ中心の設計
    • 厳密に定義されたパブリックインターフェースの構築
  • これはダックタイピングとして知られてるテクニック。この章では、ダックタイピングについて勉強する

5.1 ダックタイピングを理解する

  • プログラミング言語での「型」は、変数の中身の分類を示す
  • オブジェクトは、ただ一つのインターフェースだけに応答するってわけじゃない
    • Rubyのオブジェクトは、テーマにそってマスクを変える仮面武道会の参加者みたいな感じ(型を明示的に示さない言語仕様)
  • アプリケーションによっては、特定の1つのクラスに関連しないパブリックインターフェースをいくつも定義することもある
    • 4章では、クラス「内」のインターフェースに注目した話たっだけど、ここではそれ以外のインターフェースの話。
    • ポイントは、オブジェクトが何で「ある」かではなく、何を「する」か。
    • あらゆるオブジェクトが他のオブジェクトを常に信頼して、それでいてどんな種類のオブジェクトになれる場合、設計の可能性は無限大になる。めちゃめちゃ柔軟な設計を作れる
  • 柔軟性を賢く利用する方法
    • クラスをまたいだ型を認識すること
      • クラスをまたいだ型というのは、ダックタイプのパブリックインターフェースのこと
    • クラス内で作るパブリックインターフェースの型パターンと同じくらい入念に作る(第4章でのパターンの話)
  • ダックタイプとは
    • オブジェクトのクラスが何であろうとそのメソッドが呼び出せれば良しとするプログラミングスタイルのことをダックタイピングという
    • ダックタイプを説明する最善の方法は、ダックタイプを使わないとどうなるかを検討してみると良い
    • この本の言い回しだと、ダックタイプは、「クラスをまたいだ型、インターフェース」を表してるっぽい
  • ダックを見逃してるパターン1(以下、サンプルコードとシーケンス図)
    • 4章の図4.6に近い例

  • ダックを見逃してるパターン2(以下、サンプルコードとシーケンス図)
    • 要件が変わって旅行の準備がさらに複雑になった場合を想定したもの
    • このサンプルコードは、自身を出口のない苦境に陥れるプロセスの最初の一歩。重要なメッセージを見つけられなかったときに書いてしまうようなコードで、依存満載。よくない。
    • このようなコードを書いちゃう原因は、prepareがMechanicインスタンスを想定しているという考えからこうなちゃってる。(心の奥底では、引数はMechanicであると思っちゃってる)
    • caseとかで、引数となるインスタンスのパターンを切り替えるようにしてるけど、これだと依存が爆発的に増える書き方
    • シーケンス図をみても、複雑な感じになってる

  • ダックを見つける
    • ダックを見つけられず依存満載のコードから、依存を取り除く鍵となるのは、「Tripのprepareメソッドは単一の目的を果たすためにあるので、その引数も単一の目的を共に達成するために渡されてくるということを認識すること」です
    • 共通の目的を考えると、prepareメソッドが何を必要とするかという話になって、それは「旅の準備をすること」だと言える。
    • 次の図は、ダックを生み出した場合を表した図
      • これまは、prepaeメソッドは、引数のクラス別で処理方法を変えてたけど、ここでは、prepare_tripに応答できる複数のPreparerインスタンスが引数にくることを想定するようになった

  • 上記の図を表したサンプルコード
  • ダックタイピングの影響
    • もともとprepareメソッドは具象クラスに依存してた。変更後は、ダックタイプに依存するようになった
    • 最初のコードはの具象性は、コードを理解しやすいが、拡張に危険が伴う
    • ダックタイプ化されたコードは、抽象的で理解力が求められるけど、拡張が容易になった
    • 具象化と抽象化のコストの緊張は、オブジェクト指向設計で基本的なこと
  • ポリモーフィズム
    • 多様性、多相性
    • 同じメッセージに応答できる能力のこと
    • メッセージの送り手は、受けてのクラスを気にする必要が無く、受け手は、それぞれが独自化した振る舞いを提供する

5.2 ダックを信頼するコードをかく

  • ダックタイプの実装自体は、比較的簡単
  • ダックタイプが必要であるかを見つけることと、そのインターフェースを抽象化するのがむずい
  • 隠れたダックを認識する。次のものはダックで置き換えれる
    • クラスで分岐するcase文
    • kind_of?とis_a?
    • responds_to?
  • もっとも簡単なダックタイプは、単純にパブリックインターフェースの取り決めとしてだけ存在してるもの(この章でダックタイプ化したサンプルコードもこの類に入る)
    • いくつか、異なるクラス(Mechanic,Driver,TripCoordinator)にprepare_tripを実装することで、異なるクラスたちをPreparerとして抽象的にフル回せるようにした
  • ダック間でコードを共有する
    • この章でのPreparerダックたち(Mechanic,Driver,TripCoordinator)は、それぞれが各クラスで独自の振る舞いを用意してる。実装は共有していない
    • ダックタイピに慣れてくると、ダックタイプを実装するクラスは振る舞いも幾らか共有する必要があることに気づく。
      • このあたりは7章で説明する(コードを共有するダックタイプ)
  • 賢くダックを選ぶ
    • kind_of?やis_a?、responds_to?を使われてても、あえてダックにしないほうが良い場合もある
    • 例えば、HashやIntegerのような、Rubyのコア機能へ依存してるとかだと、コアクラス自体が安定してるものと考えて良いので、あえてダックにしなくてもいいという感じ

5.3 ダックタイピングへの恐れを克服する

  • 静的型付け言語と動的型付け言語の比較
  • 静的型付け言語は、それぞれの変数の型とメソッドのパラメーターを明示的に宣言する
  • 動的型付け言語だと、この要求を省く
  • 静的型付け支持者があげる静的型付けのメリット
    • コンパイラコンパイル時にかたのエラーを発見してくれる
    • 可視化された型情報は、文書の役割も果たしてくれる
    • コンパイルされたコードは最適化され、高速に動作する
  • 動的型付け支持者があげる動的型付けのメリット
  • ダックタイピングは、動的型付けの上に成り立つ

ダックタイピングまとめ

  • メッセージこそ、オブジェクト指向アプリケーションの中心にあるもの
  • メッセージはパブリックインターフェースを介して、オブジェクト間で交換される
  • ダックタイピングは、これらのパブリックインターフェースを特定のクラスから切り離し、何であるかではなく、何をするかによって定義される。仮想の型を作る
  • ダックタイピングは、根底にある抽象を表し、柔軟性を高めてくれる

全体的な感想

  • この章の説明を読むとダックタイプについてわかった気になるけど、実際のコードをかくときとかでこの考えを利用できるかは、また別の話のように思えるので、実務レベルで使えるようになるため、この考えが含まれてるコードをたくさん読む、そしてたくさん手を動かす、ってことを進めていきたい

  • ここまで読むのに時間かかり過ぎだー