1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GRASP(General Responsibility Assignment Software Patterns)— オブジェクトに責務を割り当てる9つの原則

1
Posted at

はじめに

オブジェクト指向設計で最も難しいのは、「どのクラスにどの責務を持たせるか」 という判断です。

GRASP(General Responsibility Assignment Software Patterns)は、この判断を体系的に行うための9つの原則・パターンです。Craig Larman が著書 Applying UML and Patterns(邦題: 実践UML)で提唱しました。

SOLID原則 がクラス設計の「品質基準」であるのに対し、GRASP はオブジェクトへの 「責務の割り当て方」 に焦点を当てた原則です。

GRASP の9つのパターン一覧

# パターン 日本語名 一言で
1 Information Expert 情報エキスパート 情報を持つクラスが責務を持つ
2 Creator 生成者 オブジェクトを生成すべきクラスを決める
3 Controller コントローラ システムイベントを受け取るクラスを決める
4 Low Coupling 疎結合 クラス間の依存を最小限にする
5 High Cohesion 高凝集 クラスの責務を焦点を絞って集中させる
6 Polymorphism 多態性 型による条件分岐をポリモーフィズムで置き換える
7 Pure Fabrication 純粋造形 ドメインにない人工的なクラスを作る
8 Indirection 間接化 中間オブジェクトで結合を減らす
9 Protected Variations 保護的変容 変化しやすい部分をインターフェースで隠す

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

「責務の遂行に必要な情報を持つクラスに、その責務を割り当てる」

GRASP の最も基本的なパターンです。「このデータを持っているのは誰か?」を考えれば、責務を割り当てるべきクラスが自然に決まります。

<?php

class OrderItem {
    public function __construct(
        private int $price,
        private int $quantity
    ) {}

    // OrderItem が価格と数量を持っている → 小計の計算は OrderItem の責務
    public function getSubtotal(): int {
        return $this->price * $this->quantity;
    }
}

class Order {
    private array $items = [];

    public function addItem(OrderItem $item) {
        $this->items[] = $item;
    }

    // Order が OrderItem の一覧を持っている → 合計の計算は Order の責務
    public function getTotal(): int {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item->getSubtotal();
        }
        return $total;
    }
}

OrderItem が価格と数量を知っているので小計の計算を担当し、Order が全アイテムを知っているので合計の計算を担当します。

2. Creator(生成者)

「オブジェクトの生成は、そのオブジェクトと密接な関係を持つクラスが行う」

クラス B がクラス A のインスタンスを生成すべき条件:

  • B が A を集約している(contains / aggregates)
  • B が A の生成に必要な初期化データを持っている
  • B が A を記録している
<?php

class Order {
    private array $items = [];

    // Order が OrderItem を集約している → Order が OrderItem を生成する
    public function addItem(int $price, int $quantity) {
        $this->items[] = new OrderItem($price, $quantity);
    }
}

OrderOrderItem を集約しているので、OrderItem の生成は Order が行うのが自然です。

3. Controller(コントローラ)

「システムイベントを受け取る責務は、システム全体またはユースケースを表すクラスに割り当てる」

UI(ビュー)がビジネスロジックを直接処理するのではなく、コントローラが仲介します。

<?php

// 悪い例: ビューがビジネスロジックを直接実行
// <button onclick="order.complete()">

// 良い例: コントローラが仲介
class OrderController {
    public function __construct(
        private OrderService $service,
        private PaymentGateway $payment
    ) {}

    public function complete(int $orderId) {
        $order = $this->service->findById($orderId);
        $this->payment->charge($order->getTotal());
        $this->service->markAsCompleted($order);
    }
}

コントローラは「何をすべきか」を知っていますが、「どうやるか」はドメインオブジェクトやサービスに委譲します。

4. Low Coupling(疎結合)

「クラス間の依存関係を最小限にし、変更の影響範囲を限定する」

疎結合は他の GRASP パターンの根底にある原則であり、独立した目標というよりも、設計判断の 評価基準 として使います。

<?php

// 密結合: OrderService が具体的な MySqlOrderRepository に依存
class OrderService {
    public function __construct() {
        $this->repository = new MySqlOrderRepository(); // 具体に依存
    }
}

// 疎結合: インターフェースを介して依存
class OrderService {
    public function __construct(private OrderRepository $repository) {} // 抽象に依存
}

SOLID原則の DIP(依存性逆転の原則) と同じ考え方です。

5. High Cohesion(高凝集)

「クラスの責務を焦点を絞って集中させ、関連性の高い処理だけを1つのクラスにまとめる」

疎結合と対になる概念で、同様に設計判断の 評価基準 です。

<?php

// 低凝集: 1つのクラスが無関係な責務を持つ
class UserManager {
    public function createUser() { /* ... */ }
    public function sendEmail() { /* ... */ }    // メール送信は User の責務?
    public function generateReport() { /* ... */ } // レポート生成は User の責務?
}

// 高凝集: 責務ごとにクラスを分離
class UserService {
    public function createUser() { /* ... */ }
}

class Mailer {
    public function sendEmail() { /* ... */ }
}

class ReportGenerator {
    public function generateReport() { /* ... */ }
}

SOLID原則の SRP(単一責務の原則) と同じ方向性です。

6. Polymorphism(多態性)

「型による条件分岐は、ポリモーフィズム(多態性)で置き換える」

<?php

// 悪い例: 型による条件分岐
class PaymentProcessor {
    public function process(string $type, int $amount) {
        if ($type === 'card') {
            // カード支払い処理
        } elseif ($type === 'bank') {
            // 銀行支払い処理
        }
        // 支払い方法が増えるたびに分岐が増える
    }
}

// 良い例: ポリモーフィズムで置き換え
interface PaymentMethod {
    public function pay(int $amount);
}

class CardPayment implements PaymentMethod {
    public function pay(int $amount) { /* カード支払い */ }
}

class BankPayment implements PaymentMethod {
    public function pay(int $amount) { /* 銀行支払い */ }
}

SOLID原則の OCP(開放閉鎖の原則) と同じ考え方です。

7. Pure Fabrication(純粋造形)

「ドメインモデルに存在しない人工的なクラスを作り、高凝集・疎結合を実現する」

情報エキスパートに従うと特定のクラスに責務が集中しすぎる場合に、ドメインには存在しない便宜上のクラスを作ります。

<?php

// 情報エキスパートに従うと Order がDB保存も担当してしまう
class Order {
    public function save() {
        // DB接続、SQL実行... → Order の責務が肥大化
    }
}

// Pure Fabrication: ドメインにない「リポジトリ」クラスを作る
class OrderRepository {
    public function save(Order $order) {
        // DB保存の責務はリポジトリに分離
    }
}

RepositoryServiceFactory といったクラスは、ドメイン概念には存在しないが設計上必要な Pure Fabrication の典型例です。DDD(ドメイン駆動設計)ではこれらを「サービス」と呼びます。

8. Indirection(間接化)

「2つのクラス間に中間オブジェクトを置くことで、直接的な結合を避ける」

<?php

// 直接結合: OrderService が PaymentGateway を直接使う
class OrderService {
    public function checkout(Order $order) {
        $gateway = new StripeGateway();
        $gateway->charge($order->getTotal());
    }
}

// 間接化: PaymentProcessor を介して結合を減らす
interface PaymentProcessor {
    public function charge(int $amount);
}

class StripePaymentProcessor implements PaymentProcessor {
    public function charge(int $amount) { /* Stripe API */ }
}

class OrderService {
    public function __construct(private PaymentProcessor $processor) {}

    public function checkout(Order $order) {
        $this->processor->charge($order->getTotal());
    }
}

Adapter パターンや Facade パターンは、Indirection の具体的な実装例です。SOLID原則の DIP とも重なります。

9. Protected Variations(保護的変容)

「変化しやすい部分をインターフェースで隔離し、他の要素への影響を最小限にする」

「将来変わりそうな箇所(不安定点)」を特定し、安定したインターフェースで包むことで、変更の波及を防ぎます。

<?php

// 税率計算は国や法律によって変化しやすい → インターフェースで隔離
interface TaxCalculator {
    public function calculate(int $amount): int;
}

class JapanTaxCalculator implements TaxCalculator {
    public function calculate(int $amount): int {
        return (int)($amount * 0.10); // 消費税 10%
    }
}

class USTaxCalculator implements TaxCalculator {
    public function calculate(int $amount): int {
        return (int)($amount * 0.08); // Sales Tax 8%
    }
}

class OrderService {
    public function __construct(private TaxCalculator $taxCalculator) {}

    public function calculateTotal(int $subtotal): int {
        return $subtotal + $this->taxCalculator->calculate($subtotal);
    }
}

税率の計算方法が変わっても、OrderService には影響しません。SOLID原則の OCPDIP の実践そのものです。

GRASP と SOLID の関係

GRASP と SOLID は対立するものではなく、異なる角度から設計を導く補完的な関係にあります。

GRASP 対応する SOLID 原則 共通する考え方
Information Expert SRP 責務を適切なクラスに集中させる
High Cohesion SRP 1つのクラスの責務を絞る
Low Coupling DIP 依存関係を最小化する
Polymorphism OCP 条件分岐をポリモーフィズムで置き換える
Protected Variations OCP + DIP 変化する部分を抽象で隔離する
Indirection DIP 中間層を介して依存を逆転させる

SOLID が「良い設計の特徴(What)」を定義するのに対し、GRASP は「どう設計するか(How)」を示すと言えます。

GRASP を実務で活用するポイント

最初に考えるべき3つ

9つのパターンすべてを意識するのは大変です。まずは以下の3つを意識すると、自然と良い設計に近づきます。

  1. Information Expert: 「この情報を持っているのは誰か?」→ そのクラスに責務を置く
  2. High Cohesion: 「このクラスの責務は焦点が絞られているか?」→ 散漫なら分割
  3. Low Coupling: 「このクラスは他のクラスに依存しすぎていないか?」→ 抽象を介す

設計に迷ったときの問いかけ

  • 「この処理に必要なデータを一番知っているクラスはどれか?」 → Information Expert
  • 「このオブジェクトを作るべきなのは誰か?」 → Creator
  • 「このクラスの責務が多すぎないか?」 → High Cohesion / Pure Fabrication で分離
  • 「この2つのクラスは直接つながる必要があるか?」 → Indirection / Low Coupling
  • 「ここは将来変わりそうか?」 → Protected Variations でインターフェースを導入

まとめ

GRASP は「どのクラスにどの責務を持たせるか」を体系的に判断するための9つのパターンです。SOLID原則と組み合わせることで、より実践的で保守性の高い設計が可能になります。

まずは Information Expert / High Cohesion / Low Coupling の3つを意識するところから始め、設計の判断に迷ったときに他のパターンを引き出す、という使い方がおすすめです。

参考記事・データ

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?