◇Pile Up◇ --Keenag Blog--

プログラミング備忘録ブログです。C#、WPFの記事が中心となります。

【デザインパターン】ChainOfResponsibilityパターン

はじめに

本エントリーは某社内で実施したデザインパターン勉強会向けの資料となります。
書籍「Java言語で学ぶデザインパターン入門」をベースに学習を進めていますが、 サンプルコードはC#に置き換えて解説します。

ChainOfResponsibilityパターンとは

複数のオブジェクトをチェーン(鎖)のようにつないでおき、要求を処理することが可能なオブジェクトにわたるまで、要求を受け流していくパターンです。


サンプルプログラムのクラス図

ChainOfResponsibilityパターンを使用したサンプルプログラムを紹介します。
ChainOfResponsibility_class.JPG


各クラスの役割

クラス名 役割
Issue 発生した問題を表すクラス。問題管理番号(IssueNumber)を持つ
Support 問題解決のサポートを行う抽象クラス
LimitSupport 問題解決のサポートを行う具象クラス(指定した番号未満の問題を解決)
OddSupport 問題解決のサポートを行う具象クラス(奇数番号の問題を解決)
SpecialSupport 問題解決のサポートを行う具象クラス(特定の問題を解決)
Program Supportたちの連鎖を作り、問題を発生させる動作テスト用クラス

Issueクラス

Issueクラスは、発生した問題を表現するクラスです。IssueNumberは問題を管理する番号です。
コンストラクタで、IssueNumberを設定します。

namespace ChainOfResponsibilitySample
{
    public class Issue
    {
        public int IssueNumber { get; private set; }

        public Issue(int issueNumber)
        {
            IssueNumber = issueNumber;
        }
    }
}

Supportクラス

Supportクラスは、問題を解決する連鎖を作るための抽象クラスです。
_nextSupportフィールドは、次の問題解決先を差します。
SetNextSupportメソッドは、次の問題解決先を設定します。
Resolveメソッドは、サブクラスで実装することを想定した抽象メソッドです。戻り値がtrueのときは要求が処理されたことを表し、falseのときは要求はまだ処理されていない(次のサポート先に問題解決を任せる)ことを表します。
SupportIssueメソッドは、Resolveメソッドを呼び出し、戻り値がfalseなら、次のサポートに問題解決を任せます。 次のサポートがいない場合は、連鎖の最後となり、サポートできなかった旨を通知するメッセージを出力します。

using System;

namespace ChainOfResponsibilitySample
{
    public abstract class Support
    {
        private string _staffName;

        private Support _nextSupport;

        public Support(string staffName)
        {
            _staffName = staffName;
        }

        public Support SetNextSupport(Support nextSupport)
        {
            return _nextSupport = nextSupport;
        }

        public void SupportIssue(Issue issue)
        {
            if (Resolve(issue))
            {
                Console.WriteLine("問題" + issue.IssueNumber + "は" + _staffName + "が解決しました。");
            }
            else if (_nextSupport != null)
            {
                _nextSupport.SupportIssue(issue);
            }
            else
            {
                Console.WriteLine("問題" + issue.IssueNumber + "は現在のサポート体制では解決できませんでした。");
            }
        }

        protected abstract bool Resolve(Issue issue);

    }
}

LimitSupportクラス

LimitSupportクラスは、limitIssueNumberで指定した問題管理番号未満の問題を解決するクラスです。

namespace ChainOfResponsibilitySample
{
    public class LimitSupport : Support
    {

        private int _limitIssueNumber;

        public LimitSupport(string staffName, int limitIssueNumber) : base(staffName)
        {

            _limitIssueNumber = limitIssueNumber;
        }

        protected override bool Resolve(Issue issue)
        {
            return issue.IssueNumber < _limitIssueNumber;
        }
    }
}

OddSupportクラス

OddSupportクラスは、問題管理番号が奇数の問題を解決するクラスです。

namespace ChainOfResponsibilitySample
{
    public class OddSupport : Support
    {
        public OddSupport(string staffName) : base(staffName)
        {
        }

        protected override bool Resolve(Issue issue)
        {
            return issue.IssueNumber % 2 == 1;
        }
    }
}

SpecialSupportクラス

SpecialSupportクラスは、指定した問題管理番号のみ問題を解決するクラスです。

namespace ChainOfResponsibilitySample
{
    public class SpecialSupport : Support
    {
        private int _specialNumber;

        public SpecialSupport(string staffName, int specialNumber) : base(staffName)
        {
            _specialNumber = specialNumber;
        }

        protected override bool Resolve(Issue issue)
        {
            return issue.IssueNumber == _specialNumber;
        }
    }
}

Programクラス

動作確認クラスでは、まず各サポートのインスタンスを作成しています。次にSetNextSupportメソッドを使用して、サポートの順番を設定しています。
そして、6件の問題を発生させ、LimitSupportであるNagatomoさんに1次処理をお願いしています。

using System;

namespace ChainOfResponsibilitySample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Support limitSupport = new LimitSupport("Nagatomo", 3);
            Support oddSupport = new OddSupport("Kagawa");
            Support SpecialSupport = new SpecialSupport("Honda", 4);

            limitSupport.SetNextSupport(oddSupport).SetNextSupport(SpecialSupport);

            var IssueCount = 6;
            // 6件の問題が発生
            for (int i = 1; i <= IssueCount; i++)
            {
                limitSupport.SupportIssue(new Issue(i));
            }

            Console.ReadKey();
        }
    }
}

実行結果

実行結果は以下のようになります。

問題1はNagatomoが解決しました。
問題2はNagatomoが解決しました。
問題3はKagawaが解決しました。
問題4はHondaが解決しました。
問題5はKagawaが解決しました。
問題6は現在のサポート体制では解決できませんでした。

問題1,2は3未満のため、LimitSupportで、Nagatomoが解決できます。
問題3はNagatomoでは解決できないため、解決処理の責任がKagawaに移ります。OddSupportのKagawaは、問題3は解決できますが偶数である問題4は解決できません。
したがって、問題4の解決処理の責任はHondaに移ります。SpecialSupportのHondaは問題4のみ解決することができます。
問題5は3以上且つ奇数のため、Nagatomo→Kagawaと責任が受け流され、Kagawaが解決します。
問題6は、全てのサポートで解決が不可能であるため、サポートできない旨のメッセージが表示されています。


ChainOfResponsibilityパターン使用によるメリット

  • 利用者側は、内部構造を意識することなく、連鎖関係のある任意のオブジェクトに要求をすることで、適切な処理者によって要求が処理される
  • 処理者側は、自分ができる処理のみ対応すれば良いので、簡潔に処理が記述できる

ChainOfResponsibilityパターンを使わないほうが良い場合

 利用者と処理者の関係が固定的で、処理速度が重要な場合は使用しないほうが良い


振り返り

ChainOfResponsibilityパターンとは 複数のオブジェクトをチェーン(鎖)のようにつないでおき、要求を処理することが可能なオブジェクトにわたるまで、要求を受け流していくパターンです。


サンプルコード

以下に公開しています。
https://github.com/Keenag/DesignPatternStudyGroup/tree/master/ChainOfResponsibilitySample