オブジェクト設計スタイルガイドを読んだので印象に残ったポイントと感想を書く。
感想
privateスコープについて
恥ずかしながらprivateスコープのことを「同一インスタンスのみからアクセス可能」だと思っていたが、「同一クラスからもアクセス可能」らしい。
頻繁に使用することはないだろうが、イミュータブルオブジェクトのモディファイアメソッド内なんかでは役立ちそう。
class Position
{
/** (略)コンストラクタ **/
/**
* この書き方だと、コンストラクタ引数が増えた場合にこのメソッドも修正が必要だが
*/
public function toTheLeft(int $steps): Position
{
return new Position(
$this->x - $steps,
$this->y,
);
}
/**
* この書き方ならその必要がない
*/
public function toTheLeft(int $steps): Position
{
$copy = clone $this;
$copy->x -= $steps;
return $copy;
}
}
サービスの依存
- サービスが依存する他のサービスや設定はコンストラクタで渡す
- サービスのコンストラクタ引数は全て必須にする
- 注入はコンストラクタのみ、セッタによる注入はしない
- サービスが行うタスクに関連するデータはメソッド引数として渡す
あたりが印象に残った。
自動車か何かの組み立て工場で例えると
- ベルトコンベア、作業員、その他作業に必要な機械←コンストラクタで注入
- 組み立て対象の部品←メソッドの引数として渡す。
のような感じだろうか。依存の注入をセッタでしてしまうと、生産ラインを動かすタイミングで作業員がいない、みたいなことが起こりうるので良くない。
オブジェクトのミュータブル、イミュータブルの話
- イミュータブルオブジェクトを優先する
- PHP標準のDateTimeで意図しない挙動を踏んだことがあるのでよく分かる。
- モディファイアメソッド
- ミュータブルオブジェクトではコマンドメソッド
- イミュータブルオブジェクトでは変更されたコピーを返す
標準関数なんかも破壊的な変更をするようなものは使い方を誤るとバグの原因になるため、オブジェクト設計に限らずイミュータブルなものを優先するのは重要な考え方だと感じる。
クエリメソッド、コマンドメソッド
- システムの境界を超える場合、クエリ・コマンドのいずれに対しても抽象を定義する。
- テスト時には、クエリはスタブ、コマンドはモックにする。
- コマンドは副作用があるので呼び出し回数(と引数)の検証をすべき←特に印象深い
テストダブルに関しては、クエリメソッドに対しても呼び出し回数の検証を書いてしまい、本体コードの変更に伴ってテストがコケる、といったことを度々経験しているので、明確に意識するようになった。ホワイトボックステストにせざるを得ない場合でも多少はテストの壊れやすさを緩和できそう。
リードモデル
あるユースケースでエンティティの属性の読み取りのみをしている場合、責務を分割し、そのユースケースに特化した読み取り専用のオブジェクトを新たに定義する=リードモデル
以前とあるアプリケーションにて履歴画面を表示する機能を実装した際に、履歴に表示する内容をプロパティに持った読み取り専用のHistory
オブジェクトを作ったことがある。これもある種のリードモデルだと言ってよさそう。
その他
- 名前付きコンストラクタ
- メソッド名にドメインの名前を使う
- 通常のコンストラクタをprivateにして塞ぐ
- 想定する使い方を第三者に強制する意味で重要
- (感想)名前付きコンストラクタはクラス内の上の方、privateコンストラクタのすぐ下にまとめて定義しておきたい。下の方に散らばっていると埋もれて気づかれなくなりそう
- ドメインイベントやイベントリスナの概念、あまり馴染みがなかったので新鮮だった
- サービスの振る舞いの変更
- 抽象に依存して実装を交換可能にする
- 「振る舞いの変更」のために継承は使うな
- こういった設計の本を読むと十中八九これを言っている気がする。みんな継承で痛い目見てきたんだなぁ
- 使い所:型の厳密な階層の定義