『オブジェクト指向設計実践ガイド』を2回目読んだら今度は理解できた


以前こんなブログを書きました。

blog.m-ito27.com

「勉強にはなった部分がたくさんあるが、意味が分からない点も多かったのでもう少し力をつけてまた読みたい」といったことを書きました。

ということで、約1年半経ったのでリベンジで読んでみました。
すると、今度はすんなり理解できる部分が多くなっていて、少しは成長できているな、と嬉しくなりました。

なるほどと思った点と感想を簡単にまとめました。

第1章 オブジェクト指向設計

世界をあらかじめ決められた手続きの集まりと考えるのではなく、オブジェクト間で受け渡されるメッセージの連続としてモデル化する。

読書メモ: Aが何かをして、Bがこうなった、という事象を、決まった流れとして考えるのではなく、「Aが何かをする」「Bがこうなる」というそれぞれは別物として考え、そのやりとりを考えるのがオブジェクト指向設計というもの、という理解をしました。(うまく書けないですが)

設計の目的は「あとにでも」設計をできるようにすること

読書メモ: 面白い表現ですが、確かにこういう言語化もできるなと納得しました。ストンと腹落ちする表現だと感じました

第2章 単一責任のクラスを設計する

クラスが単一責任かどうかを見極めるには、

  • クラスの持つメソッドを質問に言い換えたときに意味を成す質問になっているか
  • 1文でクラスを説明できるか(「それと」が含まれていると、 複数の責任を負っている可能性あり。「または」が含まれるなら、互いに関係のない責任を負っている可能性あり)

  • 書籍の例では、Gearの中でWheel.newするのではなく、wheelインスタンスを渡すようリファクタリングしている。
    これにより、GearWheelというクラス名を知っていることや引数の数や順番を知らない状態にできる。

  • ポイントとしては、Gearクラスは、「Wheelインスタンスdiameterメソッドを持っていることを知っている」のではなく、「単に@wheeldiameterメソッドを持っていることを知っている」のみということ。(ダックタイピングの思考)

依存方向の管理

「自身より変更されないものに依存しなさい」

  • GearWheelクラスのことや引数の順番、数を知っているのは具象的なコードに依存している状態。 一方で、diameterメソッドを持っているオブジェクトである、ことを知っている状態は抽象化されたものに依存している状態と言える。

第4章 柔軟なインターフェースをつくる

  • ドメインオブジェクトから考えて、何をさせるか、を考えるのではなく、オブジェクト間で交わされるメッセージに注意を向けることが必要であることが必要。(書籍の例で言えば、Customersuitable_tripsを送ること自体はなんら問題なく、考えるべきはその受け手だ、という話)

第5章 ダックタイピングでコストを削減する

  • ダックタイピングはまさに「メッセージに注目した」技術。 書籍の例だと、特定のクラスのインスタンスに依存しているのではなく、prepare_tripメソッドを返すオブジェクトであることに依存している。というもの。

第6章 継承によって振る舞いを獲得する

読書メモ:変数にtypeやcategoryといった類の名前がついているときは、要注意。クラス以内でifでそのカテゴリーごとに処理が分かれる箇所が多数発生している場合、継承という選択が良いかもしれない。

  • 階層構造を作るのはコストがかかるため、重複するコードがいくつか発生することを許しても継承するというコードを遅らせるという判断もあり得る。

  • 継承は、具象クラスから抽象的な振る舞いを抽出して抽象クラスに押し上げる方向で行うこと。逆になると具象的な振る舞いが抽象クラスに置き去りにされ、抽象クラスが信頼できないものになる。

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

抽象スーパークラス内のコードを使わないサブクラスがあってはなりません。

読書メモ: これは守れていないことも多そう。一部のサブクラスでしか使わないなら、スーパークラスではなくサブクラスに定義しないと間違った振る舞いを与えるサブクラスが生まれる。

継承する側でsuperを呼び出すようなコードを書くのは避けましょう

読書メモ: 上記にも書いたフックメソッドを使うようにして、スーパークラスアルゴリズムを知っておく責務から解放するべきというもの。super使ってしまうことは多いと思うので、注意して確認してみたい。

第8章 コンポジションでオブジェクトを組み合わせる

  • 継承が親クラスの機能を全て引き継ぐ(is_a)関係なのに対し、コンポジションはあるクラスが他のクラスのオブジェクトを含む(has_a)関係を表す。

  • 継承と違い、各クラスが独立するため、影響範囲の測定のしやすさ等でメリットがある。

一般的なルールとしては、直面した問題がコンポジションによって解決できるものであれば、まずはコンポジションで解決することを優先するべきです。継承の方がより解決法であるとはっきり言い切れないときはコンポジションを使いましょう。

読書メモ: Railsアプリにおけるコンポジションの考え方は前島さんの記事が分かりやすかった。https://tech.medpeer.co.jp/entry/2017/11/08/120000

第9章 費用対効果の高いテストを設計する

テストのセットアップに苦痛が伴うのであれば、コードはコンテキストを要求しすぎています。1つのオブジェクトをテストするために、ほかのオブジェクトをいくつも引き込まなければならないのであれば、そのコードは依存関係を持ちすぎています。

読書メモ: これはRailsのモデルspecを考える上でも参考になりそう

  • プライベートメソッドが多い場合はそれらの処理を別のクラスに切り出せないか疑う。

  • テストを書く際、メソッド内で送信するメッセージについては、副作用のないクエリメッセージの場合はテストしない(受信するクラス側でテストを書くべき)。 ただし、副作用のあるコマンドメッセージの場合は送信することをテストする必要はある。(ただしメソッドの中身までは見ない。これも受信するクラス側でテストを書くべき。)

読書メモ: コマンドメッセージの例は、メールの配信で考えられそう。例えばモデルのメソッドの中でメール送信している場合、(モックを使うなどして)メール送信のメソッドを呼ぶこと自体はテストするべきだが、どんなメールが送られれるかは、メールクラスの単体テストでテストされるべき、という話。確かに重複をなくすという点では良さそう