C#でデザインパターン「Chain of Responsibility」

C# コンピュータ
C#

「Chain of Responsibility」要求を処理できるオブジェクトを連鎖させ、要求を順に渡していくパターンだそうです。
サンプルプログラムで確認してみたいと思います。

ソースコード

// 要求を処理するインターフェース
interface IHandler
{
    IHandler SetNext(IHandler next);
    void Handle(string request);
}

// 要求を処理する抽象クラス
abstract class Handler : IHandler
{
    protected IHandler? _nextHandler;

    public IHandler SetNext(IHandler next)
    {
        _nextHandler = next;
        return next;
    }

    public virtual void Handle(string request)
    {
        if (_nextHandler != null)
        {
            _nextHandler.Handle(request);
        }
    }
}

// 具体的な処理クラス
class ConcreteHandler1 : Handler
{
    public override void Handle(string request)
    {
        if (request == "Request1") // nextHandlerがnullの場合処理しない
        {
            Console.WriteLine("ConcreteHandler1 が " + request + " を処理しました。");
        }
        else
        {
            base.Handle(request);
        }
    }
}

class ConcreteHandler2 : Handler
{
    public override void Handle(string request)
    {
        if (request == "Request2")
        {
            Console.WriteLine("ConcreteHandler2 が " + request + " を処理しました。");
        }
        else
        {
            base.Handle(request);
        }
    }
}

class ConcreteHandler3 : Handler
{
    public override void Handle(string request)
    {
        if (request == "Request3")
        {
            Console.WriteLine("ConcreteHandler3 が " + request + " を処理しました。");
        }
        else
        {
            base.Handle(request);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        // チェーンを構築
        IHandler handler1 = new ConcreteHandler1();
        IHandler handler2 = new ConcreteHandler2();
        IHandler handler3 = new ConcreteHandler3();

        handler1.SetNext(handler2).SetNext(handler3);

        // 要求を処理
        handler1.Handle("Request1");
        handler1.Handle("Request2");
        handler1.Handle("Request3");
        handler1.Handle("Request4"); // どのハンドラーも処理できない要求
    }
}

実行結果

dotnet run
ConcreteHandler1 が Request1 を処理しました。
ConcreteHandler2 が Request2 を処理しました。
ConcreteHandler3 が Request3 を処理しました。

解説

IHandlerインターフェイスと派生したクラスで構成されているため、実行時オブジェクトはIHandler型として操作することが出来ます。

IHandler handler1 = new ConcreteHandler1();

.SetNext()に次に実行するオブジェクト(Handl)をセットすることで処理を連鎖させる順番を決定しています。

handler1.SetNext(handler2).SetNext(handler3);

この場合要求した処理がRequest1であればhandler1Handleメソッドで処理され、Request1でない場合、親クラスのHandle()メソッドを呼び出し.SetNext()で指定したオブジェクト(Handl)に処理を移行します。

処理の移行部分はclass Handlerで実装され、前述した.SetNext()で次のオブジェクトをnextHandlerにセットし、Hanlder.Handl()が呼び出された場合、次のオブジェクト(Handl)のHadle()を呼び出しています。
もし、nextHandlerに次のオブジェクト(Handl)がセットされていない場合(nextHandlerがnull)、処理は行いません。

if (_nextHandler != null) // nextHandlerがnullの場合処理しない

以上のことを踏まえて実行結果を見ると、Request1~3は処理されますがRequest4は処理されていません。
また、処理の要求はhandler1のHanle()メソッドに行っていますが、リクエストによってhandler2及びhandler3で処理されていることが確認できます。

感想

「Chain of Responsibility」のパターンは同じインターフェイスを持つオブジェクトの特性を生かし、連鎖的に処理を行うことが出来ます。eメールを処理するメールサーバーの様に、宛先が自分のドメインでない場合他のメールサーバーにメールを転送する、たらい回しシステムに似ているように感じました。
interfaceやabstract classの良い使用例としても覚えておきたいパターンだと思いました。

デザインパターンを学習しようと思ったきっかけ

以前、Java言語の学習がてら、Javaで書かれたデザインパターンの書籍を購入して、サンプルコードを試しながら何故これが良いパターンなのか理解できませんでした。その後PHPで動く小規模なWebサイトを作るようになり、PHPで書かれたデザインパターンの書籍を購入してたところ、その内容が具体的に開発に役立てる場面とつながって感動した記憶があります。

ただ、素晴らしいテクノロジやテクニックでも使わないでいるとあっという間に忘れてしまいます。

それで数年ぶりにデザインパターンに挑戦しようと思ったのは、生成AIでプログラムを書いてもらう場合、デザインパターンで依頼することが出来て便利だということに気が付いたからです。似たような事例で、設計手法なんかも覚えておくと良さそうです、あとプログラミング全般で使われる用語集なんかも作っておくと良さそうな感じがします。

コメント