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

オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方 | Sandi Metz, 髙山 泰基 |本 | 通販 | Amazon

重要だと思った箇所をメモに残した。 八、九章で力尽きてしまったのでまた気が向いたら読む。

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

設計とは

設計とは、同一の製品をつくる組み立てラインではなく、アトリエなのです。

原則

  • SOLID 原則
    • 単一責任 (Single Responsibility)
    • オープン・クローズド (Open-Closed)
    • リスコフの置換 (Liskov Substitution)
    • インターフェース分離 (Interface Segregattion)
    • 依存性逆転 (Dependency Inversion)
  • DRY (Do not repeat yourself)
  • LoD (Low of Demeter)

手続き型言語との対比

手続き型言語は、データと振る舞いは完全に別物になっているのが特徴。Ruby はデータと振る舞いを1つのオブジェクトにまとめる。Ruby は文字列もオブジェクトであり、言語構文に組み込まれているわけではない。Ruby ではプログラミング言語に期待されるデータ型の全てに対し、前もってクラスが用意されている。

まとめ

オブジェクト指向の目的は「変更を容易にすること」にある。よい設計は、理論を実践に変換する能力にかかっている。

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

凝集度

クラス内の全てがそのクラスの中心的な目的に関連していれば、「凝集度が高い」と言える。

DRY

DRY はただ同じ記述を繰り返すな、と言っているわけではない。単一責任のクラスを実現すればどのような振る舞いもただ1箇所にのみ存在するようになる。原則を満たした結果として表出するのが「DRY という状態」であり、DRY にすること自体が目的ではない。

変更を歓迎するコード

  • インスタンス変数の隠蔽(カプセル化
    • 「クラス自身からインスタンス変数を隠蔽する」のはインスタンス変数の直接参照にリスクがあるから
    • 直接参照は変更に弱いためラッパーメソッドで抽象化し、必要な知識を最小限に留めるが目的だと解釈
  • カプセル化とは
  • 本書内における「メッセージを送る」とは
  • データ構造の隠蔽
    • 複雑な構造への直接参照は混乱を招く
    • Ruby の Struct は配列などのデータ構造に関する知識(とりわけどのインデックスにどのデータがあるなど)を剥がすのに便利
    • Struct とは
      • 新しいクラスを作るほどではないが、いくつかの属性を1つに束ねておくのに便利

第三章: 依存関係を管理する

依存関係とは

オブジェクトとオブジェクトの間に生まれる関係の1つ。A が B のことをどれだけ知っているか、それを知っていることは適切なことなのか、を考える必要がある。A を変更するとき、B も変更しなければいけないなら、B は A に依存していると言える。

疎結合なコードを書く

  • 依存オブジェクトの注入
    • オブジェクトのクラスではなく「送ろうとしているメッセージ」こそが重要!!
    • この視点の逆転こそがオブジェクト指向の真髄
    • これがダックタイピングに通ずる道になる
    • 特定の振る舞いを持つ任意のオブジェクトであれば誰とでも共同作業できる

依存方向の選択

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

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

インターフェースとは

クラス内にあるメソッドのこと。パブリックインターフェースは外部から呼ばれることを想定して公開しているメソッドを指す。

他の意味のインターフェースとして、要求されるメソッドを実装するクラスはどんなクラスであれその「インターフェース」のように振る舞うというものがある。これは「型」としてのインターフェースであり、ダックタイピングを扱う上で重要な概念になる。

インターフェースの定義

レストランとお客さんの例が分かりやすい。厨房では多くのことが行われるが、お客さんに公開されるのはメニューだけ。どの料理を頼むかだけを指定すればよく、それが中華鍋で作られるのか、レンジで作るのかは知る必要がない。お客さんが「料理の仕方」を知ってしまうとき、料理方法が変わったらお客さんにも訂正しないといけなくなる。

パブリックインターフェース・プライベートインターフェース

パブリック | プライベート

  • クラスの
    • 主要な責任を明らかにする | 実装の詳細に関わる
  • 外部から実行され
    • る | ない
  • 変更され
    • にくい | やすい
  • 依存するのは
    • 安全 | 危険
  • テストで
    • 文書化される | されないことが多い

コンテキストを最小限にする

パブリックインターフェースを構築するときは、そのパブリックインターフェースが他者に要求する「コンテキストが最小限」になることを目指す。

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

ダックタイピングとは

インターフェースで定義したメソッドを持つオブジェクトはその型として扱う(それが本当は何であれ)。重要なのは「何であるか」ではなく「何をするか」なのだ。これが冒頭にもあったオブジェクトの「クラスではなくメッセージが重要だ」という主張につながる。

隠れたダックを認識するために

以下が出てきたらダックタイピングを導入する余地があると考える

  • クラスで分岐する case 文
  • kind_of?
  • respond_to?

具象的なコードの危険性

具象的なコードは理解するのは簡単だが、拡張するにはコストを伴う。いつだって抽象は分かりにくいが、その拡張性は大きな力になる。

ダックを信頼する

オブジェクトを信頼する。信頼に足るオブジェクトを設計するのが設計者の仕事。

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

継承とは

本質的には「メッセージの自動委譲」の仕組みと言える。オブジェクトが理解できなかったメッセージの転送経路を定義するもの。メッセージの自動委譲によるコード共有方法には「モジュール」もある。

スーパークラスの作り方

継承のルール

  • オブジェクトが「一般 - 特殊の関係」になっている
  • 正しいコーディングテクニックを使っている

正しいコーディングテクニックとは

スーパークラスとサブクラスを疎結合にする

「フックメッセージ」を作る。フックメッセージは、サブクラスがぞれに合致するメソッドを実装することで、情報を提供できるようにするための専用のメソッド。具体的にはスーパークラスでメッセージ送信と実装の両方を行い、サブクラスで実装をオーバーライドする。サブクラスはスーパークラスについて知るべきことを少なくできる。サブクラスは実装したメソッドが何らかのオブジェクトによって、何らかのタイミングで呼び出されると想定するだけでよい。サブクラスに必要なのはテンプレートメソッドを実装するだけ(申請フォームに必要事項を記入するかのように)。

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

モジュールとは

Ruby において、ある振る舞いをオブジェクト (クラスやクラスのインスタンス) に混ぜ入れる方法のこと。メソッドの集合に名前をつけてグルーピングできる。ダックタイピングがメソッドのシグネチャを共有するのみだったのに対し、一箇所に定義された特定の振る舞い (多くの場合は複数のメソッドから成る) をオブジェクト間で共有することができる。

クラスとモジュール

「である (is-a)」「のように振る舞う (behaves-like-a)」の違い。クラスは揺るぎないが、モジュールは役職(ロール)のように取り外し可能。

extend

include がクラスにメソッド探索の経路を追加する (つまり応答できるメッセージが増える) のに対し、extend は何をしてくれるのか。extend はモジュールの振る舞いをオブジェクトに直接追加する。クラスをモジュールで extend すると「そのクラス自体に」クラスメソッドとして追加される。クラスのインスタンスを extend すると「そのインスタンス自体に」インスタンスメソッドとして追加される。これはクラスも単なるオブジェクトに過ぎないことを表す。

リスコフの置換原則 (LSP)

SOLID 原則の「L」。スーパークラスが使えるところではサブクラスが使えるという原則。派生型は上位型と常に置換可能であるということ。

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

後で書く。

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

後で書く。