Menu
Coddy logo textTech

継承よりコンポジション

CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 41/70。

継承はクラス間に強い結合を生み出します。CarVehicleを継承すると、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 を動的に変更したい場合は、コンポジションを選択します。

challenge icon

チャレンジ

簡単

継承よりもコンポジションの強力さを示す通知システムを構築しましょう。複雑な通知クラスの階層を作成する代わりに、交換可能な動作コンポーネントを与えて NotificationSender を構成します。

コードを3つのファイルに整理します。

  • Behaviors.cs:通知の送信における異なる側面を処理する2つの動作クラスを Notifications namespace に作成します。
    • FormattingBehavior - Format(string message) method を持ち、先頭に [ALERT] 、末尾に [END] を付けた message を返します
    • DeliveryBehavior - Deliver(string channel, string message) method を持ち、Sending via {channel}: {message} 形式の string を返します
  • NotificationSender.csNotifications namespace に、コンポジションを使用して動作を組み合わせる NotificationSender class を作成します。sender は次の要件を満たす必要があります。
    • FormattingBehaviorDeliveryBehavior の両方のインスタンスを private fields として含む
    • delivery channel name を格納する Channel property(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(例:EmailSMS
  • 送信する message

notification sender で Send() を呼び出した結果を print します。

たとえば、input が EmailServer 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: メッセージを送信して結果を出力する
        
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: C#オンラインコンパイラ