リスコフの置換原則
SOLID の L - サブタイプはスーパータイプと置換可能でなければならない原則
SOLID設計原則
リスコフの置換原則とは
リスコフの置換原則 (Liskov Substitution Principle, LSP) は、SOLID 原則の L にあたる設計原則で、Barbara Liskov が 1987 年に提唱した。「サブタイプのオブジェクトは、スーパータイプのオブジェクトと置換しても、プログラムの正しさが保たれなければならない」。
違反の例
class Rectangle {
constructor(protected width: number, protected height: number) {}
setWidth(w: number) { this.width = w; }
setHeight(h: number) { this.height = h; }
area() { return this.width * this.height; }
}
class Square extends Rectangle {
setWidth(w: number) { this.width = w; this.height = w; } // 正方形は幅=高さ
setHeight(h: number) { this.width = h; this.height = h; }
}
// LSP 違反: Rectangle を期待するコードが壊れる
function doubleWidth(rect: Rectangle) {
rect.setWidth(rect.area() / rect.height * 2); // 幅を2倍に
// Square だと height も変わるため、期待と異なる結果になる
}
正方形は数学的には長方形の特殊ケースだが、setWidth の振る舞いが異なるため、LSP に違反する。
準拠の例
// ✅ LSP 準拠: 共通のインターフェースで振る舞いを統一
interface Shape {
area(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
area() { return this.width * this.height; }
}
class Square implements Shape {
constructor(private side: number) {}
area() { return this.side * this.side; }
}
// どちらも Shape として安全に使える
function printArea(shape: Shape) {
console.log(`Area: ${shape.area()}`);
}
LSP 違反のサイン
- サブクラスのメソッドが例外をスローする (親クラスではスローしない)
- サブクラスが親クラスのメソッドを空実装にする
instanceofで型チェックして分岐する必要がある- サブクラスが親クラスの事前条件を強化する
TypeScript での実践
// ❌ LSP 違反: ReadonlyArray は push できないのに Array を継承
function addItem(arr: number[]) {
arr.push(42); // ReadonlyArray を渡すとランタイムエラー
}
// ✅ LSP 準拠: 読み取り専用の型を使う
function sumItems(arr: readonly number[]): number {
return arr.reduce((a, b) => a + b, 0);
}
他の SOLID 原則との関係
LSP は開放閉鎖原則 (OCP) の前提条件だ。サブタイプが安全に置換できなければ、拡張に対して開いた設計は実現できない。
LSP 違反の判定基準
| 条件 | 違反? | 説明 |
|---|---|---|
| サブクラスが例外を投げる | ✅ | 親クラスの契約に反する |
| 事前条件を強化 | ✅ | 親より厳しい制約 |
| 事後条件を弱化 | ✅ | 親より弱い保証 |
| 不変条件を破る | ✅ | 親の不変条件を維持しない |
| メソッドをオーバーライドして何もしない | ✅ | 振る舞いの変更 |
リスコフの置換原則を扱う関連書籍も多い。