リスコフの置換原則を破る継承の例
以下に、「リスコフの置換原則(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 Name | Cat has a Name |
LSPの観点 | × 置換できない | ○ 置換が不要 |
設計の柔軟性 | × 型混乱・誤用の可能性 | ○ 意味的に正しい |
6. 教訓
- 「共通のプロパティがある=継承すべき」とは限らない
- 継承すべきか迷ったら「is-a関係」が成り立つかを確認
- 共通化したいだけなら、「委譲(has-a)」が安全
訪問数 10 回, 今日の訪問数 1回
ディスカッション
コメント一覧
まだ、コメントがありません