リスコフの置換原則

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 違反の判定基準

条件 違反? 説明
サブクラスが例外を投げる 親クラスの契約に反する
事前条件を強化 親より厳しい制約
事後条件を弱化 親より弱い保証
不変条件を破る 親の不変条件を維持しない
メソッドをオーバーライドして何もしない 振る舞いの変更

リスコフの置換原則を扱う関連書籍も多い。

関連用語