Practical Object-Oriented Design in Ruby

5 14 21 24 30 59 65 71 81 88 92 105 110 125 141

オブジェクト指向なデザインの本。要するにRubyでの再利用性、耐変更性を考慮した正しい書き方。 デザインパターンの本だと思っていたが、違う。

1. Object-Oriented Design

  • 世界は手続き型である。時間は流れ、イベントはひとつひとつ起きる。
  • 世界はオブジェクト指向でもある。「人が猫を踏んでしまったとき[というプログラムを明示することはない。猫と人間というオブジェクトがあり、その相互作用が自動的に決まる。世界はオブジェクトの一連の相互作用であるとみなせる。
  • OODの反対: 世界を事前定義された手続きの塊として見る。
  • OOD: 世界をオブジェクト間を通過する一連のメッセージとして見る。本質的にプログラムの技法というより、世界の見方である。
  • 一連のメッセージは自らについての情報を交換する: OODは依存関係を扱うことについてだといえる。
  • 物事は変化する。変化は予測できない。変化に対応できるようにしておく。あとに戻れないような決定的な決断をせず、常に移動できる空間を残しておく。
  • 行数ではなく、将来かかるコストで測定するべきだが、難しい。
  • 従来の方法とアジャイルでは、デザインの意味が異なる。前者は完全な文書化、後者はどう書くか。
  • デザインの妥協は現在は楽だが、将来のコストを大きくさせることになる。つまり、未来から時間を借りてきているといえる。=技術負債。
  • デザインの損益分岐点はプログラマーの技術に依存する…経験の浅いプログラマーは設計が報われるポイントに達することがない…。
  • OODはオブジェクトとオブジェクトの間を通過するメッセージで構成される。
  • 手続き型言語のデータ型はデータが何であるかを示す。考えられるすべてのデータ型と操作が備わっている。しかし全く新しいデータ型を作ることはできない。データとふるまいには溝がある。
  • データは変数にパッケージ化されてふるまいに渡される。ふるまいは何でもさせられるが、追跡ができない。データは、ふるまいが毎朝送り出す子供のようなものだ。
  • オブジェクト指向言語は、データとふるまいを分けない。データ+ふるまい=オブジェクト。
  • オブジェクトは互いにメッセージを送り合うことで一方のふるまいを引き起こす。
  • rubyはstring型の代わりにstring objectを持つ。文字列を操作する操作は、言語の構文ではなく文字列オブジェクト自体に組み込まれている。独自のstringofデータを持つが、そのほかは同じ。データはカプセル化され、公開するか否かはオブジェクトがそれぞれ決める。
  • 文字列オブジェクトは独自の操作を提供するため,rubyは文字列データ型について知る必要がない。1つのオブジェクトが別のオブジェクトにその方法を送信するだけでよい。
  • メソッドとは、ふるまいの定義である。属性は、変数の定義である。
  • クラスから生み出されるインスタンスは、ふるまいは共通だが持つデータはそれぞれ独立している。
  • Rubyは多くの事前定義されたクラスが付属している。これが手続き型言語でいうところの型。Stringクラスはstringの定義である。
  • オブジェクト指向言語はそれ自体がオブジェクトを使用して構築されている。ここがポイント。
  • たとえば文字列クラスはそれ自体がオブジェクトで、Class classのインスタンスである。
  • すべてのstringオブジェクトは、固有のデータをもったStringクラスのインスタンスであり、すべてのclassオブジェクトは,固有のデータを持ったClassクラスのインスタンスである。

2. Designing Classes with a Single Responsibility

  • オブジェクト指向システムの基盤はメッセージで設計の中核であるが、最も目に見えるのはクラス。ふーん。
  • すぐ動くようにすること、後で楽に変更できることが必要。両方を満たす基準はとても難しい。
  • 問題は技術的な知識の問題ではなく、組織の問題。どう書くかを知ってはいても、どこに置くかを知らないのだ。
  • メソッドはクラスで定義される。作ったクラスは永遠にアプリケーションをどう考えるかに影響を与える。仮想世界を作り、下流のすべての人に想像力を強いる。
  • このように大切なんだが、プロジェクトの初期においてはそううまくいかない。まだ知らないからだ。多くの決定をするうちに変更の必要が出てくる。その日が設計に対する審判の日だ。
  • 「カンタンに変更できる」というときの「カンタン」はどういう意味か?
    • 予期せぬ副作用がない
    • 小さな変更は対応する小さな変更をするだけでいい
    • 再利用がしやすい
  • そのときのコードは次のようなものを持っている(頭文字でT.R.U.E):
    • 変更を加える最も簡単な方法は、それ自体が簡単に変更できるコードを追加すること?
    • 透明性: 変更の結果が明確であること
    • 妥当性: 変更のコストは変更によるメリットに釣り合う
    • 再利用性: すでにあるコードは新しい、未知の文脈で再利用しやすい
    • 模範となる: コード自体が、品質を維持するためにコードを変更する人を動機づける
  • 一つの責務を持つクラスを作る。できる限り小さくて便利な。
  • なぜなのか、自転車による例。
  • ギアは足が一回りするたびに移動距離を変更することで機能する。つまりペダルの回転ごとにホイールがどれくらい回転するかを変更する。ギア比率。
  • 自転車講義がはじまり、それを題材にコードを書いていく…なんか面白い
  • メソッドが必要とする引数の変更は既存のすべての呼び出し元のメソッドを破壊する(当たり前だが、こうして書くと違う印象)
  • 再利用可能なクラスは、ごちゃごちゃしてない、明確に定義されている。それらは組み合わせることによってさらに新しい使い方を得ることができる。
  • 2つ以上の責務を持つクラスは再利用しにくい。
  • クラスのすべてのメソッドが関連性が深いとき、「凝集性が高い」という。同時にクラスの目的や責務は1つで明確であることで、扱いやすくなる。
  • 開発しているものについて今知っていることはそう多くない。未来は確実に今より知っている。いい決断には情報が必要だ。決定的な決断を引き延ばそう。
  • データではなく、ふるまいに依存する。ふるまいはメソッドでキャプチャされ、メッセージを送信して呼び出される。1つの責務をもつクラスを作った場合1箇所にすべてのふるまいがあるのでDRY原則に基づくことになる…1つの場所を変更するだけでほかのすべてが変更できる。
  • インスタンス変数に直アクセスするのではなく、アクセサメソッドの中に入れる。attr_reader, attr_accessor
  • データをメッセージを理解するオブジェクトのように処理することは問題がある。アクセス権限の問題、データとオブジェクトの混同の問題。??
  • データ構造を隠す。入り組んだデータ構造への直接の参照は避ける。データが何かわかりにくく、変更しにくいから。Rubyでは意味と構造を分離するのが容易である。
  • 構造に関するすべての情報を独立した1つの処理用のメソッドに入れる。データを利用する呼び出し元のメソッドは、複雑な構造を知ることなく処理できる。Structクラス: 明示的なクラスを書かずにアクセサメソッドを使用して多数の属性をまとめる便利な方法。構造体。28P
  • 「単責任をどこでも」
  • イテレーションと分離することで再利用しやすくする。意味上で分離して組み合わせる…あとでわかりやすい!し再利用もしやすい。単責任を徹底する。

3. Managing Dependencies

  • 依存の方向が重要。
  • 引数をハッシュで渡すと、順番に依存しない。大量のオプションがあり、多くのユーザの可能性があるフレームワークなどで有効。
  • 自分より変化の少ないものに依存する…依存性の方向を考える。
  • 依存される数と変更にさらされるかで、4つのマトリックスができる。変化が多く、依存が多いのは危険である。変更するときに同時にいくつも変更する必要があるから。やりにくい。

4. Creating Flexible Interfaces

  • クラス間のコミュニケーションのパターンが必要最小限であるとよい。それぞれのオブジェクトは独立性を保っていて、変更がしやすい。
  • レストランの例: 厨房ではプライベートメッセージが飛び交っている。客はメニュー(インターフェース)を使って、注文する。客が厨房に入って料理するのは歓迎されない。
  • 実際に目に見えるような、データと動作を持つオブジェクトを、ドメインオブジェクトと呼ぶ。たとえば客、バイク、ルート…。
  • コードを書く前に、全体を設計する必要がある。UMLは、オブジェクト指向の設計を伝達する効率的な方法である。
  • メッセージのやりとりを明確にする。クラスベースのデザインからメッセージベースへ。
  • オブジェクトの責任はどこにあるか明確にできる。
  • それぞれのオブジェクトが何の情報を必要としているかを考える。
  • 客クラスは、ほかの場所に属し、変更される可能性のある実装に自身をバインドする責任をもっている。
  • 送信者が何を望んでいるかを尋ねるメッセージ、受信者に動作方法を伝えるメッセージの違い。客は、何が欲しいかを伝える必要があるが、調理方法を知る必要はないし、べきではない。
  • 何をしたいか受け取るのが、パブリックインターフェース。
  • こうして分けることで、再利用や変更がしやすくなる。人間でいえば、「あなたは私のやりたいことがわかっていて、君のパートを信頼している」この、盲目な信頼がオブジェクト指向では重要である。文脈に縛られることを少なくし、再利用がしやすい。
  • 1つのオブジェクトに1つの責任。
  • インターフェースの明確さは設計スキルを明らかにする。
  • メッセージに焦点をあわせ、オブジェクトを明確にする。

5. Reducing Costs with Duck Typing

  • オブジェクト指向のデザインの目的は変化のコストを小さくすることにある。
  • ダックタイピングは特定のクラスに関連付けられていないパブリックインターフェースである。
  • 型は、ふるまいを定義する。intだと演算、stringはconcat、など。パブリックインターフェースといえる。
  • クラスは、オブジェクトがパブリックインターフェースを得る方法の一つである。
  • 手続き型言語のメインメソッドのような、知りすぎているクラスは危険である。…変更の副作用を受けやすく、変更しにくい。
class Trip
  def prepare(prepares)
    prepares.each {|preparer|
      preparer.prepare_trip(self)}
  end
end
  
class Mechanic
  def prepare_trip(trip)
    trip.bicycles.each {|bycycle|
      prepare_bicycle(bicycle)}
    end
end
  • prepare_tripの中身はクラスによって違う。移譲して、一発でprepare_tripができる。
  • 具体的なコードは理解はしやすいがメンテナンスしにくい。抽象的なコードは最初は理解しにくいが変更しやすい。
  • ボリモーフィズム。kind_of?, is_a?, responds_to?
  • 動的型付け。コードを書くコード、メタプログラミング。
  • 動的型付けがダックタイピングを可能にする。

6. Acquiring Behavior Through Inheritance

  • 継承の核となる概念は、自動的なメッセージの移譲である。
  • スーパークラスで定義して子クラスにメッセージを送る。テンプレートメソッドパターン。ただし、これは具体クラスで埋め込むメソッドを用意し損ねる可能性があるので、例外メッセージを入れておくと親切。あとでオーバライドすると、例外が表示されない。
  • super()し忘れると間違った挙動をもたらす。よくない。コンストラクタのフックメソッドを作っておく。そうすると、子クラスには特化だけ書かれていることになる。

7. Sharing Role Behavior with Modules

  • オブジェクトごとに共通の振る舞い、それがロール。
  • ミックスインとは、名前をつけてメソッドのグループを定義すること。クラスとは独立し、どんなオブジェクトにも混ぜ入れることができる。
  • Rubyでは、混ぜ入れられるものを「モジュール」という。なるほど。includeするやつ。

8. Combining Objects with Composition

9. Designing Cost-Effective Tests

article/poodr.txt · 最終更新: 2020/12/30 00:28 by kijima