データと処理

サービスや処理といった責務側面をオブジェクト指向で設計するには、どのようにすればよいのでしょうか。
責務側面といえばユースケースです。ユースケースはどのようなレベルで書くのかで意味合いが変わります。一般的には SuD(System Under Discussion…議論対象のシステム)のレベルで書きます。アクターがそのシステムに何をして欲しいかで、システムが実現しなければならない機能となります。

ユースケースは、システムが実現すべき責務であっても、1つ1つのクラスが実現すべき責務ではありません。粒度が違うのです。クラスとして実装することを考えると、責務の粒度をユースケースより小さくしていかなければなりません。
その方法の1つに、ロバストネス図を書く方法があります。ロバストネス図は、画面等(バウンダリ)を起点として、処理(コントロール)とデータ(エンティティ)との関連を検討する図です。ユースケースシナリオに沿って、書いていきます。
この「コントロール」が「サービス」の粒度になります。3層レイヤでの1つのサービスに相当します。(トランザクション単位でもあります。)

良く設計されたドメインモデル(アクセサ程度のメソッドしかないから、データモデルのようなものである)と、ロバストネス図で抽出したコントロールがあるとしましょう。
では、コントロールをどうやってドメインモデルにマッピングしていくと良いのでしょうか。つまり、処理をデータにどうやってマッピングするのかという課題です。

「実践UML」には、GRASPというパターン集が提示されています。GRASPは汎用的な責務の割り当て規則といえるもので、今回の課題に真正面から答えてくれるものです。
GRASPでは責務(ここでいうコントロール)の割り当て先として、以下の3つが挙げられています。

  • 情報エキスパート(責務の遂行に必要なデータを持っているクラスに責務を割り当てる)
  • 生成者(あるクラスAの生成に必要なデータを持っているクラスBに、クラスAのインスタンスを生成させる)
  • コントローラ(ユーザ操作に起因する責務を、ドメインモデルには登場しない純粋架空物クラスを作って割り当てる)

高凝集性や疎結合性、バリエーション防護といったパターンが存在しますが、それらのパターンは、情報エキスパート・生成者・コントローラのいずれかを選択する際の評価基準といえます。

情報エキスパートは、データと処理をまとめようというもので、まさにオブジェクト指向的と言えます。
コントローラは、データと処理が分離することになります。コントローラは薄く作るべきで、機能分割手法に慣れ親しんだエンジニアは特に注意が必要です。コントローラはやるべき仕事を出来る限り別のオブジェクトに委譲し、アクティビティの調整と制御に没頭すべきとされています。
生成者パターンは、インスタンス生成の責務しか割り当てられないので、今回の課題の対象外です。

熱心なオブジェクト指向エンジニアは、責務を過度に情報エキスパートに割り当てようとする傾向が強いといえます。その結果、凝集度が低く、別のドメインオブジェクトとの結合度の高い設計をしてしまいます。情報エキスパートに割り当てられた責務が1つのトランザクションそのものとなり、永続化で過度に複雑なロジックを抱えこむ要因ともなってしまいます。
求めるべきは、高凝集性と疎結合性です。この2つを求めてオブジェクト指向に取り組んだのに、度が超えたために、逆に低凝集性と密結合性を追及するハメになってしまうことにもなります。

では、どうすべきでしょうか。

情報エキスパートとコントローラはバランス良く使わなければなりません。高凝集性と疎結合性を求めるなら、情報エキスパートによって割り当てられる責務は、自分自身と強く関連するオブジェクトが持つデータだけで処理できる責務に限るべきでしょう。良く設計されたドメインモデルならクラス間の関連は適度ですから、情報エキスパートが採用できることは意外と少ないのかもしれません。それ以外は、コントローラが処理することになります。ただ、間違ってもコントローラが情報エキスパートの仕事を奪ってはいけないわけで、そのバランスが難しいのです。

データと処理を盲目的にまとめることには自制が必要です。
オブジェクト指向というアイディアは、あくまで高凝集性と疎結合性を実現するためのものということを、忘れてはならないのだと思います。

コメントを残す