楽水

人々の創造が自由に表現できる舞台づくり

アプリケーション システム開発 デザインパターン

ソフトウェアの設計原則③GRASP

投稿日:

ソフトウェアの設計原則①:SOLIDの原則

という記事で、変化に強いソフトウェアの代表的な特徴

  • 保守性が高いこと
    修正箇所が局所化され、他の部分に影響しないこと(リスクの局所化)。
  • 再利用性が高いこと
    ソフトウェアが部品化され、それを再利用することができる。
  • 拡張性が高いこと
    ソフトウェアを構成するクラス同士を疎結合することで、ソフトウェアを様々な実装で拡張することができる。

を実現するための設計原則、

  • 単一責任の原則:Single Responsibility Principle(SRP)
  • 開放/閉鎖の原則:Open/Closed Principle(OCP)
  • リスコフの置換原則:Liskov Substitution Principle(LSP)
  • インターフェース分離の原則:Interface Segregation Principle(ISP)
  • 依存性逆転の原則:Dependency Inversion Principle(DIP)

について説明しました。
今回は、このうち単一責任の原則を、より詳細に検討して適用するための設計原則、
GRASP(General Responsibility Assignment Software Principle)
について、

  • GRASPとは何か
  • 依存度を下げるための原則
  • 役割分担に関する原則
  • 変動をカプセル化するための原則

という観点で解説します。

GRASPとは何か

GRASP(General Responsibility Assignment Software Principle)とは、クレーグ・ラーマンが著書実践UML 第3版 オブジェクト指向分析設計と反復型開発入門で示した設計原則で、オブジェクト指向設計において用いられる、クラスやオブジェクトに責務を割り当てる方針を導く原則のことです。

GRASPは次の9つの原則から構成されています。

これらは全て何らかのソフトウェアの課題に対する解決策であり、大半のものはソフトウェア開発プロジェクトのほとんどに共通したものです。
なので、これらの原則は新しい情報を導き出すのではなく、これまでに実証されたオブジェクト指向設計における原則をまとめた内容になります。
ここでは、9つの原則を次のように分類して紹介します。

  • 依存度を下げるための原則
    クラス間の依存度を下げることによって再利用性や拡張性を上げる設計原則。

    • 高凝集(High Cohesion)
    • 疎結合 (Low Coupling)
    • 間接化 (Indirection)
  • 役割分担に関する原則
    典型的な責務の割り当て方に関する設計原則。

    • 情報エキスパート (Information Expert)
    • 生成者 (Creator)
    • 純粋人工物 (Pure Fabrication)
    • コントローラ(Controller)
  • 変動をカプセル化するための原則
    クラスの責務のうち変動しやすい部分を抽出して、それを交換可能にすることで再利用性や拡張性を上げる設計原則。
    次のような方法があります。

    • 多態性 (Polymorphism)
      変動部分を継承させる方法。
    • 変動からの保護 (Protected Variations)
      変動部分を合成する方法。

一つ一つ見ていきましょう。

依存度を下げるための原則

ここでは、クラス間の依存度を下げることによって再利用性や拡張性を上げる設計原則について紹介します。
まず、ソフトウェアを評価する上で重要な指標である凝集度(Cohesion)と結合度(Coupling)について説明します。

  • 凝集度(Cohesion)
    システムの構成要素(モジュール、クラス、関数など)内のソースコードが特定の機能を提供すべく如何に協調しているかを表す度合い。
    凝集度が高い要素は、その内部に、特定の機能を実現するために必要な要素がギュッと詰まっている(凝集れている)というイメージ(下図)になります。
    下図の左側(高い凝集度側)は、特定の機能を実現するために必要な要素が詰まっているのに対して、右側(低い凝集度側)は、疎ら(まばら)になっていることがわかります。

    なので、凝集度が高い要素は、何の機能を実現するかが明確であり、
    修正した場合の影響の範囲が少ない
    特定の機能を持つ部品として再利用しやすい
    同じ機能を持つ部品と交換しやすい
    という点で、保守性、再利用性、拡張性が高くなります。
  • なので、凝集度が低いということは、特定の機能を実現するために不必要な機能まで入っているので、保守性、再利用性、拡張性が低くなります。

  • 結合度(Coupling)
    システムの構成要素同士の結びつきの度合い。
    結合度が低い要素同士は、互いにゆるく結びついているというイメージになります。
    構成要素同士の結合度が高いということは、互いに強く依存しているということを表し、結合度が低いということは互いの依存度も低いことを表しています。

    なので、結合度が高いと、
    一方の修正が他方に影響する
    特定の機能を持つ独立した部品として扱うことができない
    という点で、保守性、再利用性、拡張性が低くなります。

凝集度と結合度は、組み合わせて議論されることが多く、凝集度が高い要素は他との結合度が低いことが多く、逆に凝集度が低ければ結合度が高くなる傾向があります。

どのようにして凝集度を上げて、結合度を下げるか
これがGRASPにおける責務分けの根底にある課題です。

高凝集(High Cohesion)

凝集度(構成要素に含まれる機能性のまとまり)が高くなるように責任を配置するというGRASPの基本的原則です。
後述する「役割分担に関する原則」や「変動をカプセル化するための原則」を適用することによって高凝集を実現することができます。

疎結合 (Low Coupling)

結合度(ほかの要素との結び付きの強さ)が低くなるように責任を配置するというGRASPの基本的原則です。
後述する「役割分担に関する原則」や「変動をカプセル化するための原則」を適用することによって疎結合を実現することができます。

間接化 (Indirection)

2つのクラスの中間に要素を設け両者の仲介を行う責務を割り当てることで、二つのクラス間の疎結合(および再利用性)を促進するという原則です。
例えば、画面上の部品としてテキストフィールド、リストボックス、キャンセルボタンがあり、次のようなルールになっているとします。

  • リストボックスで項目が選択されると、テキストフィールドにその項目が入る
  • テキストフィールドに入力すると、リストボックスでその内容の項目が選択される
  • リストボックスで項目が選択されると、キャンセルボタンが有効になる
  • キャンセルボタンを押下すると、リストボックスが未選択の状態になる
  • テキストフィールドに入力すると、キャンセルボタンが有効になる
  • キャンセルボタンを押下すると、テキストフィールドが未入力の状態になる

各部品をクラスとして、上のルールを相互関係として実現すると次のようになります。

各クラス間が相互依存となっており強く結びついています。
そこで、互いの関係の間に仲介役(メディエーター)を置いて、仲介役がルールを制御するようにすると次のような関係になります。

間接化することで、各クラス間が相互依存が解消されて、各クラス間を疎結合にすることができます。
これは、3つ要素の例ですが、要素数が増えると、間接化による効果はさらによくわかります。

この間接化ですが、ソフトウェアを設計するときの定石を集めたGoFのデザインパターンの中では、メディエーター(Medicator)パターンとして紹介されています。

役割分担に関する原則

ここでは、典型的な責務の割り当て方に関する設計原則について紹介します。

情報エキスパート (Information Expert)

情報エキスパートは、責務を遂行するために必要な情報を全て保持しているクラスに対して責務を割り当てるべきだという原則です。
例えば、給与計算(calculatePay)を行う責務は、給与計算に必要な情報(等級など)を全て持つ社員(Employee)クラスに割り当てるという例が考えられます。

また、社員番号を取得する責務(getEmployeeNo)は、社員番号(emplyeeNo)を持つ社員(Employee)クラスに割り当てれる例も考えらえれます。

この例のように、必要な情報を設定、取得する責務は、その情報を持ったクラスに割り当てるのが自然です。
例えば、注文は、注文番号や注文日付など、それに従属する情報を保持しています。
なので、注文クラスに、注文番号や注文日付を設定、取得するための責務を割り当てます。


Java言語であれば、JavaBeanやJPA(Java Persistence API)のEntityとして情報エキスパートを実装することができます。
情報エキスパートは、私たちが言葉として使っている概念から見つけることができます。
なので、ソフトウェアを開発する際、問題領域(ドメイン)に存在する概念を分析することで情報エキスパートを洗い出すことができます。
情報エキスパートの原則を適用することによって、責務を遂行するために必要な情報を全て保持しているクラスに対して責務が集中するので、ソフトウェアの凝集度が高くなります。

生成者 (Creator)

これは、クラスを生成する責務は、そのクラスを集約するクラスのように、そのクラスを直接使用し、そのクラスを初期化するために必要な情報を持っているクラスに割り当てるべきだという原則です。
GoFのデザインパターンの中に「生成に関するパターン」があります。
これは、オブジェクトの生成過程をカプセル化することによって、生成する者(Creator)と、生成される者の間の依存度を下げ、拡張性や再利用性を上げる方法を示したものです。
これに対して、誰が生成する者 (Creator)になるべきかを示したのがGRASPの生成者(Creator)原則です。
次の例は、給与計算を専門に行うPayCalcuratorをEmployeeが保持し(集約し)、給与計算(calculatePay)をPayCalcuratorに委譲するというモデルです。

ここでは、AppServiceがEmployeeにPayCalcuratorを渡して給与計算させています。
それでは、誰がPayCalcuratorを生成してEmployeeに渡せばよいでしょうか?
次の例のように、AppServiceがPayCalcuratorを生成してEmployeeに渡すと、AppServiceがPayCalcuratorの具象クラス(この場合、PayCalcuratorA)に依存してしまいます。

そこで、次の例のように、PayCalcuratorを生成する責務を、それを集約するEmployeeに持たせることで、PayCalcuratorを生成する過程がカプセル化され、AppServiceとPayCalcuratorの間の依存関係がなくなります。

Employeeに定義されたcreatePayCalcuratorメソッドでは、指定されたPayCalcuratorの種類(CalcuratorType)に応じてPayCalcuratorAなど具体的なPayCalcuratorを生成して返します。
このcreatePayCalcuratorメソッドを、GoFのデザインパターンではファトリーメソッド(FactoryMethod)といいます。
そして、ファトリーメソッド(FactoryMethod)によってPayCalcuratorの生成を担うEmployeeが、GRASPの生成者(Creator)になります。
GRASPに従って、PayCalcuratorを集約するEmployeeが生成者(Creator)になっていることがわかります。
生成者の原則を適用することにより、クラス間の結合度が低くなり、理解のしやすさ、カプセル化が促進され、オブジェクトの再利用性を高くすることができます。

純粋人工物 (Pure Fabrication)

疎結合や、高凝集、それらから得られる再利用性が情報エキスパートで実現できない場合、それを実現するためのクラスを導入べきだという原則です。
Fabricationには偽造、作りごと、嘘という意味があるように、問題領域(ドメイン)に登場する自然な概念を表すクラス(情報エキスパート)ではなく、人為的に作るクラスを導入することで問題を解決するということです。
例を見ながら考えてみましょう。
ソフトウェアの設計原則①:SOLIDの原則という記事でも例をだしましたが、Employeeクラスが以下の3つの責務を持っているとします。

情報エキスパートの箇所でも説明したように、情報エキスパートであるEmployeeクラスは給与計算に必要な情報を持っているからcalculatePay(給与計算)は責務として妥当だと考えます。
あとの2つはどうでしょうか。
まず、reportHoursメソッドですが、これは所定労働時間を算出してレポートするメソッドなので、これをEmployeeクラスに割り当てるためには、Employeeクラスが所定労働時間の算出に必要な情報を持っている必要があります。
所定労働時間の算出に必要な情報ですが、通常、人一人の社員が持っているわけではなく、会社の人事部門が所持していると思います。
なので、Employeeクラスに、所定労働時間を算出してレポートする(reportHours)責務を持たせるのは不自然です。
情報エキスパートに、持たせるべき責務以外の責務を持たせると、その分、クラスの凝集度が下がります。
そこで、所定労働時間を算出してレポートする(reportHours)責務を持つReportHoursServiceという特別なクラスを導入することにします。

次に、saveメソッドですが、これはデータベースに社員データを保存するメソッドなので、これをEmployeeクラスに割り当てるためには、Employeeクラスがデータベースアクセスに必要な情報を持っている必要があります。
データベースアクセスに必要な情報ですが、データベースはデータを永続化する目的のシステムなので、Employeeクラスがデータベースアクセスに必要な情報を保持するのは不自然です。
情報エキスパートに、持たせるべき責務以外の責務を持たせると、その分、クラスの凝集度が下がります。
そこで、データベースに社員データを保存する(save)責務を持つEmployeeRepositoryという特別なクラスを導入することにします。

さて、ReportHoursServiceクラス、EmployeeRepositoryクラスですが、これは、問題領域(ドメイン)にある概念から導かれた情報エキスパートではなく、それぞれの責務を実現するために人為的に考えられたクラスです。

なので、ReportHoursServiceクラス、EmployeeRepositoryクラスは純粋人工物ということになります。
純粋人工物の原則を適用することによって、情報エキスパートの高凝集を確保するとともに、情報エキスパートに対する疎結合が促進され、その再利用性が高くなります。

コントローラ(Controller)

これは、システム全体を表すクラスや、ユースケースシナリオを表現するユーザインタフェースでないクラスに、システムイベントをコントロールする責務を割り当てるという原則です。
GoFのデザインパターンのにファサード(Facade)パターンというものがあります。
ファサード(Facade)とは「建物の正面」という意味で、その目的は、異なるサブシステムを、それを繋ぐ単純な操作だけを持ったファサード(Facade)クラスで結び、サブシステム間の独立性を高めることです。
GRASPのコントローラはユーザインタフェースや他システムとアプリケーションを繋ぐファサード(Facade)になります。
次の例は、社員に関するユースケースシナリオを実現する責務を持つ社員コントローラ(EmployeeController)を示したものです。

コントローラは、ユーザインタフェースからシステムに対する操作を受け取り、調整する(制御する)最初のクラスとして定義されます。
コントローラは、他のクラスに必要な処理を委譲します。
上の例の場合、社員コントローラ(EmployeeController)は、AppServiceに処理を委譲しています。
コントローラの原則を適用することによって、ユーザインタフェースや他システムに対する結合度が下がり、ソフトウェアの再利用性を高くすることができます。

変動をカプセル化するための原則

ここでは、クラスの責務のうち変動しやすい部分を抽出して、それを交換可能にすることで再利用性や拡張性を上げる設計原則について紹介します。

多態性 (Polymorphism)

クラスの機能のうち変動する部分の仕様を下位クラスに継承させ、下位クラスで具体的な振る舞い(実装)を定義させることで、再利用性や拡張性を上げるという原則です。
オブジェクトが同じ操作(機能)に対して様々な動きをする性質のことを多態性 (Polymorphism)といいます。
下図の例の場合、PayCalcuratorのcalculatePayという機能の仕様を、その下位クラスであるPayCalcuratorA、PayCalcuratorB、PayCalcuratorCが実現しています。

PayCalcuratorを利用するクラスは、実行時に、必要に応じて、その下位クラスのいずれかを選択することができます。
なので、PayCalcuratorは、選択されたクラスによって様々な動きをすることになります(多態性)。
PayCalcuratorの下位クラスは、抽象的なPayCalcuratorの実装に依存するわけではないので、部品としての交換可能性が高く、再利用性や拡張性が高いソフトウェアを実現することができます。
多態性 (Polymorphism)の原則を適用することで、そのクラスを利用する側に対する結合度を下げ、クラスの再利用性や拡張性を上げることができます。
なお、本原則は、SOLIDのリスコフの置換原則の基になる原則になります。

変動からの保護 (Protected Variations)

クラスの機能のうち変動する不安定な部分を別のクラスとして切り出して、それを合成することで、変動しない部分を保護するとともに、変動部分の拡張性を上げるという原則です。
次の例で考えてみましょう。

Employeeは、変動しやすい給与計算メソッドcalculatePayだけでなく、従業員のスキルや等級を取得するメソッドなども持っていると考えられます。
ということは、calculatePayを拡張する目的で、Employeeを継承してEmployeeAクラス、EmployeeBクラスを定義する都度、特に拡張する必要のないcalculatePay以外のメソッドも継承されることになります。
これは、拡張する必要のないメソッドの実装を増やすことになり、その分、間違った修正によるリスクや、それを検査するためのコストを上げ、保守性を下げることになります。
そこで、拡張されるべき給与計算メソッドcalculatePayだけに限定して拡張できるように、それを切り出してPayCalcuratorに委譲することで、Employeeの変動しない部分を間違った修正によるリスクから保護するとともに、多態性の原則を適用することで、変動する部分に限定して拡張できるようになります。
変動からの保護 (Protected Variations)の原則は、変動部分を切り出すことで、それを高凝集にし、交換可能性を高くすることで、変動部分の拡張性を上げることになります。
このように、変動からの保護と多態性は合わせて適用されることが多く、GoFのデザインパターンでも、これらを合わせたパターンが多くみられます。
例えば、下図は、処理から処理のやり方を戦略として切り出して合成したストラテジー(Strategy)パターンの例です。

また、下図は、オブジェクトの状態を変動部分として切り出して合成したステート(State)パターンの例です。

以上、今回はGRASPについて解説しました。

-アプリケーション, システム開発, デザインパターン

執筆者:


  1. […] ドメインモデルを構成するクラスのまとまりから、関係のないクラスへの依存関係を極力排除していき、その中だけで独立して理解できる単位にします。 これは、GRASPの疎結合を実現することになります。 GRASPの間接化や変動をカプセル化するための原則が参考になります。 […]

  2. […] ドメインに存在する概念の中には、1つの機能が単体で存在していて、エンティティや値オブジェクトなどのクラスのメソッドに属さない場合があります。 そのような場合、その機能をドメインサービスという状態を持たない(statelessな)オブジェクトとして分類します。 ドメイン知識が必要なビジネスロジックはドメインモデルが実行し、ドメインモデルを利用する側(クライアント)にドメイン知識が流出しないように注意します。 ドメインモデルのクライアントがドメイン知識を持つと、クライアントとドメインモデルの結合度が上がり、ドメインモデルのモジュール性、再利用性が下がります。 なので、エンティティや値オブジェクトでビジネスロジックが実現できない場合は、ドメインサービスとして実現するようにします。 なお、ドメインサービスは、GRASPの純粋人工物 (Pure Fabrication)に該当します。 […]

  3. […] ことで、ソフトウェアの再利用性を高める手法です。 特に、オブジェクトとの多態性(ポリモーフィズム)という性質を利用することによって、オブジェクトと再利用性を上げることが […]

  4. […] 2510;イクロサービスの特徴は、凝集度が高く、互いの結合度が低いこと&#123 […]

関連記事

【実践!DX】基幹システムの構築

DX戦略の考え方 という記事で& …

アプリケーション品質を上げるための開発方法

ここでは、次のソリューシ&#12 …

MVCとは【本来の仕組を詳しく解説】

今回は、 MVCって聞いたことが …

アプリケーションアーキテクチャ

アプリケーションアーキテ&#12 …

ドメイン駆動設計(DDD)の実装

ここでは、ヘキサゴナルア&#12 …