リスコフの置換原則を破る継承の例

以下に、「リスコフの置換原則(LSP)を破ってしまう継承の例」として、猫(Cat)と人間(Human)に共通する名前を継承で表現してしまったケースを解説する資料をまとめました。

― 「名前が共通だから継承すべき」は本当か?


1. リスコフの置換原則(LSP)とは?

定義:

派生クラス(サブクラス)は、基本クラス(スーパークラス)の置き換えとして使用できなければならない。

つまり、親クラスの代わりに子クラスを使っても、プログラムの動作が破綻しないことが原則です。


2. よくある間違った継承の例

● 「猫」と「人」に共通して”名前”がある

→ 共通化したいから、Nameクラスを継承してみよう!

public class Name
{
    public string FullName { get; set; }
}

public class Cat : Name
{
    public void Meow() => Console.WriteLine("ニャー");
}

public class Human : Name
{
    public void Speak() => Console.WriteLine("こんにちは");
}

3. 何が問題なのか?

● Cat is a Name? Human is a Name?

この構造では、以下のような使い方が可能になります:

Name n = new Cat();
Console.WriteLine(n.FullName); // OK
n.Speak(); // コンパイルエラー(Catには Speak() がない)

● 継承関係が意味を成さない

  • CatやHumanがNameという「名前情報」そのものになるのはおかしい
  • ”〜は〜である”(is-a)関係が成立していない
  • このような継承は、LSPを破っている

4. 正しい設計:委譲(コンポジション)を使う

● 「猫」や「人」が「名前を持っている」(has-a)

public class Name
{
    public string FullName { get; set; }
}

public class Cat
{
    public Name Name { get; set; }
    public void Meow() => Console.WriteLine("ニャー");
}

public class Human
{
    public Name Name { get; set; }
    public void Speak() => Console.WriteLine("こんにちは");
}

これなら以下のような使用が可能:

Cat cat = new Cat { Name = new Name { FullName = "ミケ" } };
Console.WriteLine(cat.Name.FullName); // OK

Human human = new Human { Name = new Name { FullName = "田中一郎" } };
human.Speak(); // こんにちは

5. まとめ

視点間違った設計正しい設計
関係性Cat is a NameCat has a Name
LSPの観点× 置換できない○ 置換が不要
設計の柔軟性× 型混乱・誤用の可能性○ 意味的に正しい

6. 教訓

  • 「共通のプロパティがある=継承すべき」とは限らない
  • 継承すべきか迷ったら「is-a関係」が成り立つかを確認
  • 共通化したいだけなら、「委譲(has-a)」が安全
訪問数 10 回, 今日の訪問数 1回