dasukoの技術ブログ

現役エンジニアのブログです。

Chain of Responsibilityパターン

はじめに

今回はChain of Responsibilityパターンについてご紹介します。

Chain of responsibilityパターンとは

Chain-of-responsibility パターン, CoR パターンは、オブジェクト指向設計におけるデザインパターンの一つであり、一つのコマンドオブジェクトと一連の処理オブジェクト (processing objects) から構成される。各処理オブジェクトは、処理できるコマンドオブジェクトの種類と、自身が処理できないコマンドオブジェクトをチェーン内の次の処理オブジェクトに渡す方法を記述する情報を保持する。また、新たな処理オブジェクトをチェーンの最後に追加する機構を備える。

Chain of Responsibility パターン - Wikipediaより引用

コマンドを動的に追加、削除できることも特徴です。

Chain of ResponsibilityパターンはGoFの設計パターンの一つです。

[GoFの書籍はこちら]

Chainは"鎖"、Responsibilityは"責任"という意味です。

つまり、Chain Of Responsibilityは"責任の鎖"という意味です。

チェーンに対して順番にリクエストしていき、誰が責任者なのかを決めます。

Decoratorパターンとの違い

Decoratorパターンの場合、全てのクラスがリクエストを処理しますが、Chain of Responsibilityパターンではチェーン内のクラスの1つだけがリクエストを処理します。

実装上の注意点

リクエストのSenderとReceiverの結合は避けます。

Sender側でリクエスト処理を直接実装すると、クラスが特定のReceiverに結合され、

複数のReceiverを扱えなくなり、柔軟性がなくなってしまいます。

Class図

クラス図は以下のようになります。

Handlerは複数のReceiverを所持していて、各Receiverに対して順番にリクエストします。

f:id:dasuko:20200718140057p:plain

実装サンプル

今回は抽象的なサンプルにしてみました。

API_Aは必ず失敗するAPIAPI_Bは必ず成功するAPIです。

API_AにAPI_Bを連結してRequestをします。

すると、API_Aはスキップされ、API_Bで処理がされるというわけです。

// API
abstract class API
{
    private API next;
    protected abstract bool HandleRequest();

    public API SetNext(API next)
    {
        this.next = next;
        return next;
    }

    public void Request()
    {
        if (!HandleRequest() && next != null)
        {
            next.HandleRequest();
        }
    }
}

// A API
class API_A : API
{
    protected override bool HandleRequest()
    {
        return false;
    }
}

// B API
class API_B : API
{
    protected override bool HandleRequest()
    {
        return true;
    }
}

class Client
{
    void DoRequest()
    {
        var apiA = new API_A();
        var apiB = new API_B();

        apiA.SetNext(apiB);

        apiA.Request();
    }
}

まとめ

条件によって処理を細かく分けたい時にクラスを分けて、Chain Of Responsibilityパターンで実装するととても便利だと思います。

よく見かけるのが、Loggerの実装だと思いますが、

環境によってAPIの向き先が違う(StagingだとAというURL、本番だとBというURLといった状況)の時とかに

Staging用のコマンド、本番用のコマンドとかを作ってチェーンにしてもいいかもしれません。

どのコマンドが実行されたかはわかるようにログを出力するようにしたり、識別子をリターンするようにしてもいいと思います。

参考

Chain of Responsibility パターン - Wikipedia

Chain-of-responsibility pattern - Wikipedia

デザインパターン ~Chain of Responsibility~ - Qiita