はじめに
SOLID 原則の 3 つ目は LSP(リスコフの置換原則)。
これはオブジェクト指向設計の「継承」にまつわる重要な考え方で、派生クラスが基底クラスの契約を壊さないこと を求めます。
1. 定義
リスコフの置換原則(LSP) とは:
派生クラスは、基底クラスとして置き換えても動作が破綻しないべきである。
- 基底クラスのインスタンスを使う場面で、派生クラスを差し替えても正常に動作する
- 「IS-A」の関係を乱用すると違反が起こりやすい
2. 直感的な例
LSP 違反
open class Bird {
open fun fly() = println("Flying...")
}
class Sparrow : Bird()
class Penguin : Bird() {
override fun fly() {
// ペンギンは飛べない → 例外を投げる
throw UnsupportedOperationException("Penguins can't fly!")
}
}
-
Birdを前提にfly()を呼ぶと、ペンギンだけエラーになる - これは「ペンギンは鳥だから継承で表す」とした設計が LSP違反 になる例
LSP 準拠
interface Flyable {
fun fly()
}
open class Bird
class Sparrow : Bird(), Flyable {
override fun fly() = println("Flying...")
}
class Penguin : Bird() {
fun swim() = println("Swimming...")
}
- 飛ぶ能力(Flyable) と 鳥(Bird) を分離
-
Penguinを使ってもBirdの契約は壊れない
3. Flutter / Android での例
Flutter
abstract class MediaPlayer {
void play();
}
class AudioPlayer implements MediaPlayer {
@override
void play() => print("Playing audio...");
}
class VideoPlayer implements MediaPlayer {
@override
void play() => print("Playing video...");
}
// LSP違反例
class ImageViewer implements MediaPlayer {
@override
void play() => throw UnsupportedError("Image cannot be played!");
}
→ MediaPlayer に ImageViewer を継承させたのは誤り。
→ 解決策:Displayable など別インターフェースを用意する。
Android (Kotlin)
interface Authenticator {
fun authenticate(): Boolean
}
class PasswordAuth : Authenticator {
override fun authenticate() = true
}
class FingerprintAuth : Authenticator {
override fun authenticate() = true
}
// LSP違反例
class GuestUser : Authenticator {
override fun authenticate() = throw UnsupportedOperationException()
}
→ GuestUser は認証の概念自体を持たないため Authenticator を実装すべきではない。
4. LSP 違反が招く問題
- 実行時エラーが頻発(UnsupportedOperationException など)
- APIの信頼性が低下(契約が守られていない)
- テスト容易性が下がる(モック/スタブで差し替えられない)
- クライアントコードが複雑化(型チェックや例外処理が増える)
5. 実務での適用ポイント
-
「is-a」ではなく「can-do」で考える
→ 「ペンギンは鳥」より「ペンギンは泳げる」 -
継承よりコンポジションを優先
→ 能力(interface)を組み合わせて表現する - 契約違反を起こす可能性があるなら、別インターフェースに分ける
-
レビュー時に質問する
→ 「この派生クラスは、基底クラスとして安全に置き換えられますか?」
6. まとめ
- LSP = 継承の契約を壊さないこと
- 違反は「IS-Aの乱用」で起こりやすい
- 解決策は インターフェース分離 と コンポジションの活用
- LSPを守ると APIの一貫性・テスト容易性・拡張性 が大幅に改善する