ストーリー
SRPとは
Single Responsibility Principle(単一責任の原則): クラスを変更する理由は、ただ1つであるべきである。
ここでの「責任」は「変更の理由」と同義です。あるクラスが複数の理由で変更されるなら、そのクラスは複数の責任を負っています。
SRP違反の例
// SRP違反:3つの変更理由がある
class Employee {
private name: string;
private hourlyRate: number;
private hoursWorked: number;
// 責任1: ビジネスロジック(給与計算ルールの変更時)
calculatePay(): number {
return this.hourlyRate * this.hoursWorked;
}
// 責任2: レポート(レポート形式の変更時)
generateReport(): string {
return `Employee Report\n` +
`Name: ${this.name}\n` +
`Pay: ${this.calculatePay()}`;
}
// 責任3: データ永続化(DB構造の変更時)
save(): void {
const sql = `INSERT INTO employees (name, rate, hours)
VALUES ('${this.name}', ${this.hourlyRate}, ${this.hoursWorked})`;
database.execute(sql);
}
}
このクラスは以下の3つの理由で変更される可能性があります:
- 給与計算ルールが変わった —
calculatePay()を変更 - レポートの形式が変わった —
generateReport()を変更 - データベース構造が変わった —
save()を変更
SRPを適用した設計
// 責任1: 従業員のビジネスロジック
class Employee {
constructor(
readonly name: string,
readonly hourlyRate: number,
readonly hoursWorked: number
) {}
calculatePay(): number {
return this.hourlyRate * this.hoursWorked;
}
}
// 責任2: レポート生成
class EmployeeReportGenerator {
generate(employee: Employee): string {
return `Employee Report\n` +
`Name: ${employee.name}\n` +
`Pay: ${employee.calculatePay()}`;
}
}
// 責任3: データ永続化
class EmployeeRepository {
save(employee: Employee): void {
// ORMやクエリビルダーを使用
this.db.employees.create({
name: employee.name,
hourlyRate: employee.hourlyRate,
hoursWorked: employee.hoursWorked,
});
}
}
実践的なSRPの判断基準
「この変更は誰が要求するか」テスト
高橋アーキテクトのアドバイス:
「SRPの判断に迷ったら、“この変更を要求するのは誰か?“と考えてみよう。経理部門、マーケティング部門、IT部門 — 異なるステークホルダーが変更を要求するなら、それは異なる責任だ」
現実的な分割のコツ
// 分割しすぎ(過剰設計)
class UserNameValidator { /* 名前だけ検証 */ }
class UserEmailValidator { /* メールだけ検証 */ }
class UserAgeValidator { /* 年齢だけ検証 */ }
// 適切な粒度
class UserValidator {
validate(user: UserInput): ValidationResult {
return ValidationResult.combine([
this.validateName(user.name),
this.validateEmail(user.email),
this.validateAge(user.age),
]);
}
private validateName(name: string): ValidationResult { /* ... */ }
private validateEmail(email: string): ValidationResult { /* ... */ }
private validateAge(age: number): ValidationResult { /* ... */ }
}
SRPは「1クラス1メソッド」を意味しません。「ユーザー入力のバリデーション」という1つの責任の中に複数のメソッドがあるのは自然です。
SRP適用のよくある間違い
| 間違い | 正しい理解 |
|---|---|
| 1クラス1メソッドにすべき | 1つの変更理由に対して複数メソッドは OK |
| 全てのメソッドを別クラスに | 関連する操作は同じクラスにまとめてよい |
| ユーティリティクラスは SRP 違反 | 同じカテゴリの操作をまとめるのは OK |
まとめ
| ポイント | 内容 |
|---|---|
| SRPの定義 | クラスを変更する理由はただ1つであるべき |
| 責任 = 変更の理由 | 誰が変更を要求するかで判断する |
| 適切な粒度 | 分割しすぎず、関連する操作はまとめる |
| 効果 | 変更の影響範囲が限定され、保守性が向上 |
チェックリスト
- SRPの定義を自分の言葉で説明できる
- 「責任 = 変更の理由」を理解した
- SRP違反のコードを見つけて分割できる
次のステップへ
次は SOLID の O — オープン・クローズドの原則(OCP)を学びます。「変更に閉じ、拡張に開いている」という一見矛盾した概念を理解しましょう。
推定読了時間: 30分