Chapter 9 - Practical Object Oriented Design in Ruby

Combining Object with Composition

まとめ

1つのオブジェクトを複数のオブジェクトで構成するという話。

自転車オブジェクトがPartsオブジェクト持つという例で、sparesのようなPartsオブジェクトが返答すべきメッセージは内部でPartsオブジェクトへ処理を委譲する。

外から見たら同じだけど、内部を複数のオブジェクトに分割、管理することで、SRPに基づいた設計ができ、再利用性や拡張に対して柔軟な設計となりやすくなる。

複数のオブジェクトから構成されるオブジェクトは、初期化処理が複雑になりがち。

そういった場合はFactoryパターンを活用すると、オブジェクト生成に関する知識を1つの場所に収めることができる。

is-a, has-a, behaves-like-a のどの関係性がふさわしいか、それぞれの実装のinheritance, composition, duck types(module) が持つ長所、短所を理解して判断する必要がある。

これらの判断は、情報が少ない開発初期の段階では間違いがちなので、それを受け入れて正しく修正して心構えが大事。 初期の実装にひきづられずに、情報が増えてきた際に正しく判断し、リファクタリングをしていく。

Chapter 7 - Practical Object Oriented in Ruby

Sharing Role Behavior with Modules

まとめ

dock typeの章で説明したような共通の振る舞いを発見した際に、それをモジュール化することで、コードを1つの箇所に置くことができる。

シーケンス図の各ノードに、具体的なクラス名を書くのではなく、求められていること、実際にやっていることを記述することで、モジュール化する役割の名前を見つ出すことができることがある。


ある1つのことに関して実現させようとするとき、それ以外のことを知っていないと実現できようなケースは余計な依存性があると考えることができる。

例えば、文字列に関して、空がどうかの判定にStringUtils.empty?(str)とする必要がある場合、単純にstring.empty?とできるケースと比較すると、StringUtils#empty?の存在を知っていないといけないという依存性ができる。

stringのようなコアのオブジェクトに関しては、頻繁に変更が起きることもないし、基本的な使い方はすぐに覚えることができてるのでそれほど大きな問題にはならないと思うけど、アプリケーションに定義したオブジェクトでこういった余計な依存性が増えていくと、やりたいことに対して、実現させるコストが増えそうだし、変更のコストも結構変わってきそう。


抽象的な役割を見つけた時に、対処しなければいけない問題として、どうやってその役割を実現させるかと、どこにそのコードを置くかという2つが発生する。

こういったケースでは問題を分けて対処するのがわかりやすくて、まず1つのクラスに対して、役割を実現できるようなコードを実装した後に、抽象化させる部分をモジュールに移して動作させ、その後に他の同じ役割のクラスでも動作させるようにするといい。


同じ役割を持つクラスは、その役割を果たす工程において、クライアントからは全て同じインターフェースに対応する必要がある。(同じメソッド名、引数、戻り値)

同じ役割を持っているのに、クライアントで、どのクラスかどうかで処理を分けるのはなるべく避けるべき。


継承、モジュールを使うと、構成をどんどん複雑化させることができるので注意して扱う必要がある。

これらの技術はデザイン設計の技術よりコーディング作業に密接に連携しているので、うまくできるどうかで、アプリケーションに対する影響は大きい。

Chapter 6 - Practical Object Oriented Design in Ruby

Acquiring Behavior Through Inheritance

まとめ

継承とはメッセージを自動的に転送すること。


継承を用いると、クラスの構造が複雑になるので、必要な情報が揃った時に行うのがいい。

具体的には、2つの似たような種類のクラスがあって、1つの親クラスと、2つのサブクラスに分けられそうな場合は、いくつかコードが重複してしまっていても、そのままにしておいた方がいい。(もちろんケースバイケース)

似たような属性を持つクラスが3つになった時には、親子構造を作成するための情報をかなり持つことができるので、適切な設計を作りやすくなる。(必要な情報が入るまで、現状のままにしておくことは、コストマネージメントを考える上で大事)


いざ親クラスを作ろうとなった時は、まず空の親クラスを作成し、継承関係を設定した後に、サブクラスから親クラスへ振る舞いをボトムアップさせていくやり方がいい。

この方法の場合は、サブクラスに抽象化すべきところが残ってしまう可能性がある。

ただ、抽象化しないとサブクラス間でコードが重複してしまう→親クラスにまとめるというのは自然に行われていくので、それほど大きな問題にはなりにくい。

逆の場合、先に親クラスに元のクラスをコピーし、サブクラス固有の振る舞いをサブクラスに移行するやり方の場合は、親クラスに本来サブクラスであるべき具体的な振る舞いが残ってしまう可能性がある。

このケースを修復するのは前者より難解になりやすく、親クラスに手をつけずにコードを拡張していくと、純粋な継承関係が崩壊してしまい、システム全体へと影響が広がってしまう可能性がある。


サブクラスからsuperを使って明示的に親クラスのメソッドを呼び出すのは、1つの依存性と言える。 依存性があるということは、変更時のコストに影響がある。

hookやtemplate patternなどを使うことで、依存性を避けるように、サブクラスのスペシャリティーを簡潔に表現できる設計方法を探すといい。

Chapter 5 - Object Oriented Design in Ruby

Duck Typing

まとめ

あるオブジェクトを、どのクラスのオブジェクトか?という視点で見るのではなく、どういったインターフェースを持っているか?という視点で見ること。

あるオブジェクトに対して、kind_of? 等で分岐させて、それぞれ異なるメソッドを呼び出しているようなところはダックタイピング的なアプローチが有効かどうかを考えてみる価値がある。

ダックタイピングを用いると、特定のクラス名、メソッド名に依存しなくて良くなるので、変更のコストが下がる。 しかし、具体的なクラスではなく、抽象的な表現を扱うことになるので、コードを直感的に理解しづらくなる。 なので、ドキュメントとしても有効なテストを書くことが推奨される。

基本的には、使われる側のクラスに共通のインターフェースを定義することによるコストと、クライアント側が依存する情報が増えることによるコストのトレードオフ

Chapter4 - Practical Object Orient Design in Ruby

Creating Flexible Interfaces

抜粋

第3章で頻繁に変更が起こるクラスに依存するのは危険という話があったが、この考え方はクラス内の範囲でも同じように適応できる。

クラスの持つパブリックなインターフェースは、そのクラスが持つ責任をドキュメントの役割を果たすべき。

設計をする段階のクラス候補として上がってくる、顧客、旅行、バイクなどのクラスはドメインオブジェクトと呼ばれている。

ドメインオブジェクトはほぼ必ず必要となるものだが、アプリケーション内のすべての振る舞いをこれらのクラスに定義しようとするのは危険。

設計の達人は、ドメインオブジェクト自体だけではなく、ドメインオブジェクト間で発せられるメッセージにフォーカスして、ドメインオブジェクト以外にどのようなクラスが必要になってくるかを見極める。

Ask for "What" instead of Telling "How"

シーケンス図はクラスベースデザインの視点からメッセージベースデザインの視点へ切り替えるためのツールとも言える。

上記の視点の変換は下記のような会話で表せる。

'このクラスは必要だね、何をすべきかな?' から 'このメッセージを送る必要があるんだけど、誰が受け取るべき?'

感想

シーケンス図を使ってメッセージに焦点を当てて設計を考えるというのは早速試してみたいと思った。

UMLを資料としてではなく、設計を考える際の使い捨てのツールとして使う考えも活用させていきたい。

Chapter3 - Practical Object Orient Design in Ruby

Managing Dependencies

抜粋

  • デザインのやるべきことは、それぞれのクラスが仕事をこなすために必要な最低限のことだけを知っているように、依存性を管理すること

  • dependency injection

クラスが知っていることを減らすという視点で、クラスの名前と初期化方法、メソッド名を知っているより、メソッド名だけを知っている状態の方が依存性が少ない

  • isolate vulnerable external messages

外部参照は慎重に扱うべきというのはわかる気がするけど、なんかやりすぎな気がする。 Gear#diameter では意味が変わってしまうのでは?

  • remove argument-order dependencies

ハッシュによる呼び出しが優れている。 呼び出し順の依存性がなくなり、引数の追加、削除をリスクなしに行える。

ハッシュ名を記入するのは冗長になってしまっているが(そのまま順に指定していった方がコードの量は減る)、この冗長性は現在の要求とと不確かな未来の要求の媒体として存在している。

また、ハッシュでの呼び出しはドキュメントとしても価値がある。

  • 依存性の向き

変更が多いと想像できるクラスはなるべく影響範囲が広がらないようにする。 変更が多いクラスから変更が少ないクラスへ依存するように努めることで、影響の広がりを抑える。

Abstractなものはconcreteなものより細かい変更が起こりにくいので、Abstractなものに依存する方が変更の影響を受けづらい。

感想

クラス間の依存性など、なんとなくこうした方がいいかな〜という感じで実装してきていたけど、判断基準などを言語化して明確にしていくことで、クラス間の関係性を考える基準を明確に持つことにより、考えるコストを減らせて、習慣化していければ、ミスジャッジを減らしていくことができそう?

依存性とは具体的にはどのようなものか、どのようにシステムに影響を及ぼすか、どのような管理、評価方法があるか、というようなものをしっかり定着させることができればコードの質が上がっていきそう。

英語

  • These dependencies turn minor tweaks into major undertakings where small changes cascade through the application, forcing many changes.

それらの依存性は細かい調整を、その調整の影響のあるたくさんの箇所の変更を強いられるような大きな仕事に転じさせてしまう。

  • On the face of it 一見したところでは

Chapter 2 - Practical Object Oriented Design in Ruby

Designing Classes with a Single Responsibility

抜粋

  • クラスは一つの責任しか持つべきではない。 そのクラスを一文で簡潔に説明できるかどうかが一つのバロメーター。 andやorが入る場合は、2つ以上の責任を持ってしまっている可能性が高い。

  • designの決定はしかるべき情報が揃った時にするのが一番いい。 現状のクラスがイマイチすっきりしないように感じても、情報が揃うまで手をつけづにおくのは、コストを抑える一つの考え方。

  • data[0],data[1]のような曖昧なデータ表現は避けたほうがいい。 こういった曖昧なデータを明示的にどういったデータを表すかをはっきりさせるのも一つの責任で、コードの中に散りばめてしまったら変更が大変になるので、 曖昧なデータを明示的にするコードは一つの場所にまとめるべき(DRY).

rubyではStructを使うことが一つの手段。


  • 責任の分離に関して、別のクラスを設けるほどではないけど、今のままでは気持ち悪いなというケースは大いに考えらる。

こういった場合、別のクラスに分けてしまってもいいが、Rubyの場合は、クラス内のStruct等にコードを分離しておくのも一つの手段。

こうしておくと、後から来た要望で、やっぱり別クラスにしたほうがいいなとなった時に、スムーズにコードを移植することができる。

また、そのままの状態が続いても、コードの意図が解りやすくなる。

感想

DRY(Don't Repeat Yourself)やSRP(Single Responsible Principle)について実際のコードの用いて説明されていてわかりやすかった。

曖昧なデータを使うべきではないところや、別クラスに分けるほどでもないな〜というケースは、よく遭遇するケースで、ぼんやりとケースごとに対処していたけど、解りやすく言語化されているので参考になる。

決定の保留に関してかなり頻繁に言及されていたりと、単純に原則を説明しているのではなく、実際のコストなどを考慮されていて、だいぶ実践的なのでいい感じ。

英単語

  • fraught 悲惨な

Every decision seems both permanent and fraught with peril. perilは危険

  • overwhelming 圧倒的な

These questions can be overwhelming.

  • foresee 予見する

  • cohesion 凝集

OO designer use the word cohesion to describe this concept.