継承よりコンポジション
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 41/70。
継承はクラス間に強い結合を生み出します。CarがVehicleを継承すると、Vehicleへの変更によってCarが壊れる可能性があります。コンポジションは、より柔軟な代替手段を提供します。振る舞いを継承する代わりに、クラスがその振る舞いを提供するオブジェクトを含みます。
移動や攻撃ができるゲームキャラクターを考えてみましょう。継承を使うと、複雑な階層構造を作ることになるかもしれません。コンポジションを使えば、キャラクターに必要なコンポーネントを与えるだけです。
public class MovementBehavior
{
public void Move() => Console.WriteLine("Moving...");
}
public class AttackBehavior
{
public void Attack() => Console.WriteLine("Attacking!");
}
public class Character
{
private MovementBehavior movement = new MovementBehavior();
private AttackBehavior attack = new AttackBehavior();
public void Move() => movement.Move();
public void Attack() => attack.Attack();
}重要な違いは関係性です。継承は「is-a」(Dog は Animal)を表すのに対し、コンポジションは「has-a」(Character は movement behaviorを持つ)を表します。コンポジションを使うと、実行時にbehaviorを入れ替えたり、機能を自由に組み合わせたり、脆弱な基底クラスの問題を回避したりできます。
真正的階層関係がある場合は継承を使用します。柔軟性が必要な場合、関連のないクラス間で behavior を共有する可能性がある場合、または behavior を動的に変更したい場合は、コンポジションを選択します。
チャレンジ
簡単継承よりもコンポジションの強力さを示す通知システムを構築しましょう。複雑な通知クラスの階層を作成する代わりに、交換可能な動作コンポーネントを与えて NotificationSender を構成します。
コードを3つのファイルに整理します。
Behaviors.cs:通知の送信における異なる側面を処理する2つの動作クラスをNotificationsnamespace に作成します。FormattingBehavior-Format(string message)method を持ち、先頭に[ALERT]、末尾に[END]を付けた message を返しますDeliveryBehavior-Deliver(string channel, string message)method を持ち、Sending via {channel}: {message}形式の string を返します
NotificationSender.cs:Notificationsnamespace に、コンポジションを使用して動作を組み合わせるNotificationSenderclass を作成します。sender は次の要件を満たす必要があります。FormattingBehaviorとDeliveryBehaviorの両方のインスタンスを private fields として含む- delivery channel name を格納する
Channelproperty(string)を持つ - channel name を受け取る constructor を持つ
- まず formatting behavior を使用して message を format し、次に delivery behavior を使用してそれを deliver し、最終結果を返す
Send(string message)method を持つ
Program.cs:main file で、input から channel を指定してNotificationSenderを作成し、次に input から message を送信します。構成された動作がどのように連携するかを示すため、結果を print します。
2つの input を受け取ります。
- delivery channel name(例:
Email、SMS) - 送信する message
notification sender で Send() を呼び出した結果を print します。
たとえば、input が Email と Server is down の場合、output は次のようになります。
Sending via Email: [ALERT] Server is down [END]NotificationSender は何も継承しておらず、単に formatting と delivery の動作を「has」していることに注目してください。このコンポジション approach では、sender class を変更せずにどちらかの動作を簡単に交換したり、これらの動作をまったく異なる class で再利用したりできます。
自分で試してみよう
using System;
using Notifications;
class Program
{
public static void Main(string[] args)
{
// 入力を読み取る
string channel = Console.ReadLine();
string message = Console.ReadLine();
// TODO: チャネルでNotificationSenderを作成する
// TODO: メッセージを送信して結果を出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
5ポリモーフィズムとインターフェース
コンパイル時 vs 実行時ポリモーフィズムインターフェース vs 抽象クラス複数のインターフェース明示的なインターフェースアップキャストとダウンキャストまとめ - 図形計算機8オブジェクト指向の高度な概念
継承よりコンポジションジェネリクス (クラスとメソッド)デリゲートとイベント属性とリフレクションIDisposable と using ステートメント依存性の注入 (DI) の基礎自分で練習してみよう: C#オンラインコンパイラ