オブジェクト指向
設計の実用的な定義
プログラマーは超能力者ではないので、特定の未来を予測した設計は、たいてい失敗に終わる。
実用的な設計とは、未来を推測するものではなく、未来を受け入れるための選択肢を保護するもの。選択してしまうのではなく、動くための余地を設計者に残すもの。
設計の目的は「あとにでも」設計できるようにすることであり、その第一の目標は変更コストの削減。
クラスが単一責任がどうか見極める
クラスの持つメソッドを質問に言い換えたときに、意味を成す質問になっているべき。
また、1文でクラスをかんたんに説明できるものであるべき。
クラス内のすべてがそのクラスの中心的な目的に関連していれば、そのクラスは凝集度が高い、もしくは単一責任であると言われる。
あらゆる箇所を単一責任にする
メソッドに対しても役割が何であるか質問をし、また1文で責任を説明できるようにする。これはクラスを明確にする効果がある。
クラスの唯一の目的が、他のクラスのインスタンス作成であるオブジェクトをオブジェクト指向設計では、「ファクトリー」という。
つまり、ほかのオブジェクトを製造するオブジェクトはファクトリーです。
パブリックインターフェース
- クラスの主要な責任を明らかにする
- 外部から実行されることが想定される
- 気まぐれに変更されない
- 他者がそこに依存しても安全
- テストで完全に文章化されている
プライベートインターフェース
- 実装の詳細に関わる
- ほかのオブジェクトから送られることは想定されていない
- どんな理由でも変更され得る
- 他者がそこに依存するのは危険
- テストでは、言及さえされないこともある
アプリケーションにおいて、「データ」と「振る舞い」の両方を兼ね備えた「名詞」を表すものをドメインオブジェクトと呼ぶ。ドメインオブジェクトとは、大きくて目に見える現実世界のものを表し、かつ最終的にデータベースに表されるもの。
基本的な設計の質問を、「このクラスが必要なのは知っているけれど、これは何をすべきなんだろう」から、「このメッセージを送る必要があるけれど、だれが応答すべきなんだろう」へ変えることが、キャリア転向への第一歩。
オブジェクトが存在するからメッセージを送るのではなく、メッセージを送るためにオブジェクトは存在する。
あえてキーワードを省略し、代わりにコメントやそれ用の命名規則を使い、インターフェースの「パブリック」と「プライベート」な部分を示している (たとえば Ruby on Rails では、プライベートメソッドの先頭に _ を付ける)。
コンテキストを最小限にする
パブリックインターフェースを構築する際は、そのパブリックインターフェースが他者に要求するコンテキストが最小限になることを目指す。「何を (what)」と「どのように (how)」の違いを常に念頭に置き、メッセージの送り手が、クラスがどのようにその振る舞いを実装しているかを知ることなく、求めているものを得られるようにつくる。
デメテルの法則
オブジェクトを疎結合にするためのコーディング規則の集まり。
「ドッドは1つしか使わないようにしよう」なんて言い方もされる。
パブリックインターフェースに含まれるメソッドは、次のようにであるべき。
- 明示的にパブリックインターフェースだと特定できる
- 「どのように」よりも、「何を」になっている
- 名前は、考えられる限り、変わり得ないものである
- オプション引数として、ハッシュをとる
ダックタイプはいかなる特定のクラスとも結びつかないパブリックインターフェース。
めっちゃ勉強してる
素晴らしい!
ちなみに冒頭リンク先にある
近代科学社の
『ソフトウェア工学』
は、
めっちゃごんぶと本ですけど(;´∀`)
この業界で一生喰ってきたいなら
読んどいてたぶんぜったいに
損しないと思いますよぇ~✨
ダックで置き換えられるもの。
- クラスで分岐する case 文
- kind_of? と is_a?
- responds_to?
Rails の Concern で使われるモジュール名は ***able
となっているものが多い。
あくまで慣習であり、規約ではない。
以下のアンチパターンを目にしたときは、継承を使ってコードを改善できないか疑ってみる。
1. オブジェクトが type や category という変数名を使い、どんなメッセージを self に送るかを決めているパターン
オブジェクトの関連度は高いものの、わずかに型の異なるオブジェクトが含まれている。共通のコードは抽象スーパークラスにおき、サブクラスを使って異なる型をつくる。このような構成にすると、サブクラスを追加することで新たなサブタイプ (派生型) をつくれる。これらのサブクラスが、既存のコードを変えずに階層構造を把握してくれる。
2. メッセージを受け取るオブジェクトのクラスを確認してから、どのメッセージを送るかをオブジェクトが決めているパターン
ダックタイプを見落としてしまっている。この場合、受けてになり得るオブジェクトは、どれも共通のロールを担っている。このロールはダックタイプとしてコードに落とし込まれるべき。そうすれば、受け手のオブジェクトが複数あったとしても、送り手のオブジェクトとしてはどのオブジェクトに対しても1つのメッセージを送るだけで済む。
コンポジションでは、オブジェクト間の関係をクラスの階層構造としてコードに落とし込むことはしない。コンポジションでは、オブジェクトは独立して存在する。そしてその結果、オブジェクトはお互いについて明示的に知識を持ち、明示的にメッセージを委譲する必要がある。コンポジションによって、オブジェクトは構造的に独立して存在できるようになる。しかし、それは明示的なメッセージ委譲のコストを払ってのこと。
継承とコンポジションの利用について
- 継承とは、特殊化のこと
- 継承が最も適しているのは、過去のコードの大部分を使いつつ、新たなコードの追加が比較的少量のときに、既存のクラスに機能を追加する場合
- 振る舞いが、それを構成するパーツの総和を上回るのなら、コンポジションを使う
- is-a 関係に継承を使う
- behaves-like-a 関係にダックタイプを使う
- has-a 関係にコンポジションを使う
継承を使った結果得られるコードは「オープン・クローズド (Open-Closed)」と特徴付けられる。階層構造は、拡張には開いており (open)、修正には閉じている (closed).
リファクタリングとは、ソフトウェアの外部の振る舞いを保ったままで、内部の構造を改善していく作業を指す
受信メッセージはオブジェクトのパブリックインターフェースの一部であるので、テストされなければならない。
最も良いテストとは、対象のコードと疎結合であり、すべてに対して一度だけテストをし、そしてそれが適切な場所で行われているもの。
リスコフの置換原則 (LSP)
「サブクラスはスーパークラスと置換可能でなければならない」という原則。
つまり、派生クラスは基底クラスを継承するときに、常に基底クラスの挙動を一切変えないようにすべきであるということ。