はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】責任をたらい回しするChain of Responsibilityパターンについて学ぶ

はじめに

今回は責任をたらい回しするChain of Responsibilityパターンを紹介したいと思います。

f:id:hanaaaaaachiru:20210218191300p:plain
クラス図

登場人物

冒頭でも述べた通り,Chain of Responsibilityパターンは責任(要求に応じて処理を行う人)をたらい回しにするためのデザインパターンになります。

f:id:hanaaaaaachiru:20210218193641p:plain
イメージ
名前 意味
Handler 要求を処理するインターフェイス(API)を定める役。次の人を保持しておく。
ConcreteHandler 要求を処理する具体的な役
f:id:hanaaaaaachiru:20210218191300p:plain
クラス図

Handlerのコード

Handler処理を要求次の依頼先を保持することを実装していきます。

/// <summary>
/// 要求を処理するインターフェイスを定める抽象クラス
/// </summary>
public abstract class Handler
{
    private Handler _next;

    public Handler SetHandler(Handler handler)
    {
        _next = handler;
        return handler;
    }

    public virtual void Request(SomeTask task)
    {
        if (Resolve(task))
        {
            Console.WriteLine($"タスクが{this}によって処理されました。");
        }
        else if(_next != null)
        {
            _next.Request(task);
        }
        else
        {
            Console.WriteLine($"タスクを処理することができませんでした。");
        }
    }

    protected abstract bool Resolve(SomeTask task);
}

/// <summary>
/// サンプルとして用意した,依頼する仕事を表現するクラス
/// </summary>
public class SomeTask
{
    public string Name { get; }        // 仕事の名前

    public SomeTask(string name)
        => Name = name;
}

ConcreteHandler

次にHandlerを継承した、具体的にどう処理するかそれとも次の人に任せるかを記述するConcreteHanlerを実装します。

public class ConcreteHandler1 : Handler
{
    protected override bool Resolve(SomeTask task)
    {
        if (task.Name != "TaskA") return false;

        Console.WriteLine($"{task.Name}を処理します。");
        return true;
    }
}

public class ConcreteHandler2 : Handler
{
    protected override bool Resolve(SomeTask task)
    {
        if (task.Name != "TaskB") return false;

        Console.WriteLine($"{task.Name}を処理します。");
        return true;
    }
}

動作確認

実際にこれらが動作するか動作確認を試してみましょう。

class Client
{
    private static void Main(string[] args)
    {
        // 初期化(本来であれば別の箇所で設定するのが望ましい)
        Handler handler1 = new ConcreteHandler1();
        Handler handler2 = new ConcreteHandler2();
        handler1.SetHandler(handler2);

        // 発生したタスクを処理してもらう
        // 「TaskBを処理します。」
        // 「タスクがConcreteHandler2によって処理されました。」
        handler1.Request(new SomeTask("TaskB"));
    }
}

さいごに

実用するときはHandlerに次が誰かを設定する責務は別クラスにて定義した方がよいと思います。

UnityならScriptableObjectで記述して,インスペクターやエディタ拡張で作成した自作ウィンドウから設定できるようにするなんかしても面白そうですね(実際にやったことがあるわけではない)。

またランタイムで処理の順番が容易に変えられるのも結構魅力的な気もします。

しかし動作速度は愚直にswitchする等の実装よりも遅くなってしまうのでそこだけは注意してください。

ではまた。