SOLID 原則
オブジェクト指向設計の 5 つの基本原則で、保守性と拡張性の高いコードを実現する
SOLID 原則とは
SOLID は、Robert C. Martin (Uncle Bob) が体系化した 5 つの設計原則の頭文字である。狙いは、変更が来たときに影響範囲を予測できる形にコードを保つことで、その結果として保守性・拡張性・テスト容易性が上がる。Martin 自身は 2020 年の記事でこれらを特定の言語機能に縛られない指針として説明し、リスコフ置換の原則も継承という機構の話ではなくサブタイプの関係についての原則だと述べている。
5 つの原則
5 つはいずれも「変更が来たときに何を触らずに済ませるか」を別の角度から述べたものである。
| 原則 | 名前 | 一言 |
|---|---|---|
| S | 単一責任 (SRP) | クラスの変更理由は 1 つだけ |
| O | 開放閉鎖 (OCP) | 拡張に開き、修正に閉じる |
| L | リスコフ置換 (LSP) | サブタイプは呼び出し側の想定を壊さずに置き換わる |
| I | インターフェース分離 (ISP) | 使わないメソッドに依存しない |
| D | 依存性逆転 (DIP) | 具象ではなく抽象に依存 |
S: 単一責任の原則
Martin はこの原則を「モジュールが変更される理由はただ 1 つでなければならない」と述べ、変更理由をたどると最終的に要求を出す人に行き着くと説明している。分ける基準は機能の数ではなく、同じ理由で変わるものをまとめ、違う理由で変わるものを離すことにある。次の例では、保存方法の都合・通知文面の都合・集計要件の都合という別々の理由で変更が来る処理が 1 つのクラスに同居している。
// ❌ 1 つのクラスに複数の責務
class UserService {
createUser(data: UserInput) { /* DB 操作 */ }
sendWelcomeEmail(user: User) { /* メール送信 */ }
generateReport(users: User[]) { /* レポート生成 */ }
}
// ✅ 責務ごとに分離
class UserRepository { create(data: UserInput) { /* DB 操作 */ } }
class EmailService { sendWelcome(user: User) { /* メール送信 */ } }
class ReportGenerator { generate(users: User[]) { /* レポート生成 */ } }
O: 開放閉鎖の原則
拡張点をあらかじめ抽象として切り出しておけば、機能追加は実装を 1 つ足すだけで済む。裏を返せば、どこが増えるかを読み違えた抽象は使われないまま複雑さだけを残す。次の例は通知手段が増えるという読みを interface として固定している。
// ✅ 新しい通知方法を追加しても既存コードを変更しない
interface Notifier { notify(message: string): Promise<void>; }
class EmailNotifier implements Notifier { async notify(msg: string) { /* メール */ } }
class SlackNotifier implements Notifier { async notify(msg: string) { /* Slack */ } }
// 新しい通知方法を追加: 既存コードの変更なし
class SmsNotifier implements Notifier { async notify(msg: string) { /* SMS */ } }
D: 依存性逆転の原則
上位の処理が下位の実装を直接名指しすると、保存先やクライアントを差し替えるたびに上位まで書き換えることになる。依存の向きを抽象へ向け直すと差し替えが局所化し、テストでは偽の実装を渡せる。
// ❌ 具象に依存
class OrderService {
private db = new DynamoDBClient({}); // 具象クラスに直接依存
}
// ✅ 抽象に依存
class OrderService {
constructor(private repo: OrderRepository) {} // インターフェースに依存
}
SOLID の過剰適用に注意
SOLID は指針であり、全てのコードに厳密に適用する必要はない。Lambda の単純な CRUD 関数に SOLID を厳密に適用すると、過度な抽象化で複雑になる。
| ケース | SOLID の適用度 |
|---|---|
| 複雑なドメインロジック | 厳密に適用 |
| 共有ライブラリ | 厳密に適用 |
| 単純な Lambda 関数 | 緩やかに適用 |
| スクリプト | 不要 |
SOLID 原則の関連書籍も参考になる。
この記事は役に立ちましたか?
関連用語
リスコフの置換原則
SOLID の L - サブタイプはスーパータイプと置換可能でなければならない原則
インターフェース分離の原則
SOLID の I - クライアントが使わないメソッドへの依存を強制しない原則
依存性逆転の原則
SOLID の D - 上位モジュールは下位モジュールに依存せず、両者とも抽象に依存すべきという設計原則
DRY 原則
Don't Repeat Yourself の略で、知識の重複を排除し、単一の正を維持する設計原則
Twelve-Factor App
クラウドネイティブなアプリケーションを構築するための 12 の設計原則
単一責任の原則
SOLID の S - クラスやモジュールが変更される理由は 1 つだけであるべきという設計原則