この記事のコードは全てC#で記載しています。
継承と委譲
まずは継承と委譲の違いを明確にしておきます。
趣味全開のコード例とともにですがお許しください😏
継承
- 「is-a」が成り立つ関係性
- 静的な関係を作り出すため、動的な変更が難しい
- 親クラスのコードを再利用可能
- 親クラスの責務もすべて引き継ぐ
// 親クラス
class TwiceMember
{
public void IntroduceMyself(string name)
{
Console.WriteLine($"Hello! This is {name}.");
}
}
// 子クラス
class Momo : TwiceMember
{
private readonly string NAME = "MOMO";
// コンストラクタ
public Momo()
{
// 親クラスのメソッドを使用可能
this.IntroduceMyself(this.NAME);
}
}
委譲
- 「has-a」が成り立つ関係性や特定の機能のみを使用したい場合
- 実行時に動的に機能を変更可能
- 他クラスの機能を必要に応じて利用
- クラスごとに責務を分離できる
class Momo
{
private readonly string NAME = "MOMO";
private string HairColor {get; set;} = "black";
public void ImageChange()
{
// HairSalonクラスの一機能のみを使用する
var hairSalon = new HairSalon();
this.HairColor = hairSalon.DyeHairBlond();
Console.WriteLine($"HairColor changed to {this.HairColor}");
}
}
class HairSalon
{
public string DyeHairBlond() => "Gold";
}
「has-a」の場合は継承を使うべからず
オブジェクト指向やプログラミングを学び始めた当初、私はこう思いました。
「全部継承でよくない?🤔」
わざわざ使いたいクラスをnew
して使うぐらいだったら最初に継承しておけば機能を使用できるしコード量も削減できる。なぜ役割が分かれているのか?
それは、「has-a」の関係性を持つクラス同士や一部の機能のみを使用したいケースで継承をしてしまうといくつかのデメリットがあるからなんですね
先の委譲の実装例を継承に書き換えてデメリットのついて考察していきます。
// 継承に置き換えた実装
class Momo : HairSalon
{
private readonly string NAME = "MOMO";
private string HairColor { get; set; } = "black";
public void ImageChange()
{
// HairSalonクラスの機能をそのまま使用
this.HairColor = this.DyeHairBlond();
Console.WriteLine($"HairColor changed to {this.HairColor}");
}
}
class HairSalon
{
public string DyeHairBlond() => "Gold";
}
デメリット1: 責務の混乱
継承を利用すると、子クラスが親クラスのすべての機能と責務を引き継ぎます。したがって、本来子クラスには必要のない責務まで引き継いでしまうことになります。
人であるモモちゃんが人ではないヘアサロンの責務を全て引き継ぐのは違和感がありますよね。別の実装者がクラス間の関係性を誤解して不具合を生む実装をしてしまう可能性があります。
クラスの設計は単一責任原則が基本ですが、それに反する構造となります。
デメリット2: 保守性の低下
継承を使用すると親クラスの変更が子クラスにも影響するため、常に親クラスの変更を気に掛ける必要があります。
「is-a」の関係性のように親クラスと同じ振る舞いをすることが確実でない場合、親クラスの修正が子クラスで予期しない不具合を生む可能性があります。
例えば、人以外を表すオブジェクトに対して一斉になにか実行する処理を追加実装したいとき。
HairSalon
クラスにIsHuman = false;
のようなプロパティを追加して対応する場合、Momo
クラスでIsHuman = true;
の実装が漏れると不具合に繋がる可能性が高いです。
継承の階層が深くなっている場合はこの不具合の原因を特定するにも一苦労することでしょう。
まとめ
継承は上手く利用すれば便利ですが、使用の際はクラス間の関係性を吟味する必要があります。
必ず 「is-a」 の関係性が成り立つことを確認しましょう!
この話題には「インターフェース」も深く関わってきますが長くなりそうなので今回は言及を控えました。
「インターフェース」についてはまた別の機会に...
参考