はじめに
オブジェクト指向設計で最も難しいのは、「どのクラスにどの責務を持たせるか」 という判断です。
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);
}
}
Order は OrderItem を集約しているので、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保存の責務はリポジトリに分離
}
}
Repository、Service、Factory といったクラスは、ドメイン概念には存在しないが設計上必要な 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原則の OCP と DIP の実践そのものです。
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つを意識すると、自然と良い設計に近づきます。
- Information Expert: 「この情報を持っているのは誰か?」→ そのクラスに責務を置く
- High Cohesion: 「このクラスの責務は焦点が絞られているか?」→ 散漫なら分割
- 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つを意識するところから始め、設計の判断に迷ったときに他のパターンを引き出す、という使い方がおすすめです。