SOLID原則は、オブジェクト指向設計の5つの原則をまとめたものです。読みやすく・拡張しやすく・テストしやすいコードを書くための指針として、長く支持されてきました。本記事では、SOLID原則を具体例つきで解説します。
SOLIDの5原則
- S:Single Responsibility(単一責任)
- O:Open/Closed(開放/閉鎖)
- L:Liskov Substitution(リスコフ置換)
- I:Interface Segregation(インターフェース分離)
- D:Dependency Inversion(依存関係逆転)
S: 単一責任の原則
「クラスは変更理由を1つだけ持つべき」。複数の関心事を抱えたクラスは、変更の度に副作用が増えます。
// NG:保存とメール送信の両方を担う
class User {
save() { db.save(this); this.sendEmail(); }
}
// OK:責務を分離
class UserRepository { save(u) { db.save(u); } }
class UserMailer { sendWelcome(u) { /* ... */ } }
O: 開放/閉鎖の原則
「拡張に対して開いていて、修正に対して閉じている」。新機能追加で既存コードを書き換えなくて済む設計。
// NG:if文で型分岐
function area(shape) {
if (shape.kind === 'circle') return Math.PI * shape.r ** 2;
if (shape.kind === 'square') return shape.side ** 2;
}
// OK:多態で拡張可能
class Shape { area() { throw new Error('override'); } }
class Circle extends Shape { area() { return Math.PI * this.r ** 2; } }
L: リスコフ置換原則
「派生型は基底型と置き換え可能であるべき」。継承関係でサブクラスがスーパークラスの契約を破ると違反です。
class Bird { fly() {} }
// NG:飛べないペンギンがBirdを継承
class Penguin extends Bird { fly() { throw 'cannot fly'; } }
// OK:Flyable / Walkable などインターフェースを分離
I: インターフェース分離
「クライアントは使わないメソッドに依存させない」。1つの巨大なインターフェースより、小さな複数の方が良い。
// NG:印刷もスキャンもFAXも全部できるI/F
// OK:Printable / Scannable / Faxable に分割
D: 依存関係逆転
「上位モジュールは下位モジュールに依存してはいけない。両者は抽象に依存すべき」。具体クラスではなくインターフェースに依存させ、テストや差し替えを容易に。
// NG
class OrderService {
constructor() { this.db = new MySQLClient(); }
}
// OK:抽象(インターフェース)を受け取る
class OrderService {
constructor(repo) { this.repo = repo; }
}
適用のコツ
- 「全てに当てはめる」より「変更が辛い時の指針」として使う
- テストが書きやすくなったらSOLID的に良いサイン
- 関数型・モジュール型コードでも本質は同じ
まとめ
SOLID原則は、長期保守を意識する設計の共通言語です。教科書的に全部当てはめる必要はありませんが、コードが辛くなった時に立ち戻る指針として強力。リファクタリングの判断軸として身につけておきましょう。