C#

【デザインパターン】C#で書くChain Of Responsibilityパターン

アイキャッチ(c_sharp)

こんにちは!
なかむぅです。

「オブジェクト指向」といえばデザインパターンは必ず通る道なので、おすすめの本「Java言語で学ぶデザインパターン入門第3版」を購入!

そのデザインパターンの一つ「Chain Of Responsibilityパターン」について勉強しましたが

せっかくならChain Of ResponsibilityパターンをC#で書いて動かしてみよう

と思ったので書いてみました。


我が社で一緒に働きませんか?

私が働く会社では、一緒に働いてくれるエンジニアを募集しています♪

我が社のココがアツイ!!
  • 未経験、第二新卒歓迎
  • 学歴・転職回数・離職期間不問
  • 残業は月15時間以下で残業代は全額支給
  • 経験者は月給23万円以上
  • 年収100万以上UP↑↑↑も可能
  • 長く働いていても年収が全然上がらないから、一気に年収を上げたい
  • 残業をしたくてしているわけではないので、残業代はしっかりと出して欲しい
  • やりたいことに挑戦させてもらえないから、自分がしてみたいことに挑戦したい
  • 通勤時間が長すぎるから、できるだけ短くしたい
  • もっと勉強したいから、書籍代など負担してくれるところに入りたい
  • 現場に駆り出されてからは放置プレイで、相談できる人も先輩も居ないので、自分の状況をちゃんと理解してくれるところで働きたい

上記内容に1つでも当てはまる場合は、ぜひご検討ください。

詳細ページ


Chain Of Responsibilityパターン

スマホがちゃんと動きません。

サポートセンターに電話します。

30分ほど待ってやっと繋がりました。

こちらでは対応致しかねますので、端末サポート窓口にお繋ぎ致します。

また30分ほど待ってやっと繋がりました。

おそらく故障ですね。修理サポート窓口にお繋ぎ致しますので少々お待ちください。

いつ繋がるのかと思いながら、さらに30分ほど待ってやっと最後の窓口に繋がります。

こんな感じで、たらい回しにされたことありませんでしょうか?

ある問題を解決できない場合、解決できる窓口に誘導して「たらい回し」にすることがあります。

私も携帯販売していたときは、対応がめんどくさいので、よくお客様サポートへ誘導していました。

お前もかよ

プログラムの世界でも同じように、発生した要求を処理するオブジェクトを固定的に決められない場合があります。

Chain Of Responsibilityパターンは処理するオブジェクトを固定せず、チェーンのように繋いだ複数のオブジェクトを順繰り回して、要求を処理できるオブジェクトを決定するデザインパターンです。

サポート窓口のプログラム

まず基盤となるクラスが2クラスあります。

Trouble 発生したトラブルを表現する
Support トラブルを解決するための連鎖を作る抽象クラス

そして、Supportクラスを継承した4つの子クラスが登場します。

NoSupport 何も解決しないクラス
LimitSupport 指定した番号未満のトラブルを解決するクラス
OddSupport 奇数番号のトラブルを解決するクラス
SpecialSupport 指定した番号のみを解決できるクラス

Supportを継承した子クラス達は、トラブル番号を取得して解決できるかどうかを判断する役割です。

Troubleクラス

Troubleクラスは発生したトラブルを表現するクラスです。

トラブル番号を管理しています。

Trouble.cs

namespace ChainOfResponsibilityPattern
{
	public class Trouble
	{
		private int _number; // トラブル番号

		public Trouble(int number)
		{
			_number = number;
		}

		public int GetNumber()
		{
			return _number;
		}

		public override string ToString()
		{
			return $"[Trouble {_number}]";
		}
	}
}

Supportクラス

Supportクラスはトラブルを解決するための連鎖を作る抽象クラス。

43行目のResolveメソッドは、サブクラスで実装することを想定した抽象メソッドです。

24行目のToSupportでResolveメソッドを呼び出して解決か未解決かの判断をします。

次のサポート先がある場合は、処理を終了せずに次のサポート先に回します。

Support.cs

using System;

namespace ChainOfResponsibilityPattern
{
	public abstract class Support
	{
		private string _name; // トラブル解決者
		private Support _next; // 次の問い合わせ先

		public Support(string name)
		{
			_name = name;
			_next = null;
		}

		// 次の問い合わせ先を設定
		public Support SetNext(Support next)
		{
			_next = next;
			return _next;
		}

		// 解決を調べた結果、どうするかを判断する
		public void ResultsSupported(Trouble trouble)
		{
			if (Resolve(trouble))
			{
				Done(trouble);
			}
			else if (_next != null)
			{
				_next.ResultsSupported(trouble);
			}
			else
			{
				Fail(trouble);
			}
		}

		// 解決方法を調べる
		// true:解決
		// false:未解決
		protected abstract bool Resolve(Trouble trouble);

		// 解決できた
		protected void Done(Trouble trouble)
		{
			Console.WriteLine($"{trouble} is resolved by {this}.");
		}

		// 解決できなかった
		protected void Fail(Trouble trouble)
		{
			Console.WriteLine($"{trouble} cannot be resolved.");
		}

		public override string ToString()
		{
			return $"[{_name}]";
		}
	}
}

NoSupportクラス

NoSupportクラスは何も解決しないクラス。

返すのはfalseのみです。

NoSupport.cs

namespace ChainOfResponsibilityPattern
{
	public class NoSupport : Support
	{
		public NoSupport(string name) : base(name)
		{
		}

		protected override bool Resolve(Trouble trouble)
		{
			return false;
		}
	}
}

LimitSupportクラス

LimitSupportクラスは指定した番号未満のトラブルを解決するクラス。

解決できる番号の最大値を受け取って、limitフィールドにセットして比較します。

LimitSupport.cs

namespace ChainOfResponsibilityPattern
{
	public class LimitSupport : Support
	{
		private int _limit; // 解決できる番号の最大値

		public LimitSupport(string name, int limit) : base(name)
		{
			_limit = limit;
		}

		protected override bool Resolve(Trouble trouble)
		{
			return trouble.GetNumber() < _limit;
		}
	}
}

OddSupportクラス

OddSupportクラスは奇数番号のトラブルを解決するクラスです。

OddSupport.cs

namespace ChainOfResponsibilityPattern
{
	public class OddSupport : Support
	{
		public OddSupport(string name) : base(name)
		{
		}

		protected override bool Resolve(Trouble trouble)
		{
			return trouble.GetNumber() % 2 == 1;
		}
	}
}

SpecialSupportクラス

SpecialSupportクラスは指定した番号のみを解決できるクラス。

解決できる唯一の番号を受け取り、トラブル番号と一致するかどうかをジャッジします。

SpecialSupport.cs

namespace ChainOfResponsibilityPattern
{
	public class SpecialSupport : Support
	{
		private int _number; // 解決できる番号

		public SpecialSupport(string name, int number) : base(name)
		{
			_number = number;
		}

		protected override bool Resolve(Trouble trouble)
		{
			return trouble.GetNumber() == _number;
		}
	}
}

使い方

まずはそれぞれのサポートの担当者と、処理できる番号を決めます。

担当者が処理できる番号は以下の通りです。

担当者 解決できる番号
Alice 何も解決できない
Bob 100までなら解決できる
Charlie 429番だけ解決できる
Diana 200までなら解決できる
Elmo 奇数番号なら解決できる
Fred 300までなら解決できる

そのあとは順番にSetNextメソッドでサポートする順番を決めましょう。

次のコードは何も解決できないポンコツアリスから、ある程度のことなら解決できる新人ボブに続き、専門的なことしか解決できないチャーリーに引き継いで、最終的に比較的なんでも解決できるフレッドにバトンが渡されます。

Program.cs

namespace ChainOfResponsibilityPattern
{
	class Program
	{
		static void Main(string[] args)
		{
			Support alice = new NoSupport("Alice");
			Support bob = new LimitSupport("Bob", 100);
			Support charlie = new SpecialSupport("Charlie", 429);
			Support diana = new LimitSupport("Diana", 200);
			Support elmo = new OddSupport("Elmo");
			Support fred = new LimitSupport("Fred", 300);

			// サポートする順番を決定する
			alice.SetNext(bob).SetNext(charlie).SetNext(elmo).SetNext(fred);

			// トラブル番号を採番してたらい回しにする
			for (int i = 0; i < 500; i+=33)
			{
				alice.ResultsSupported(new Trouble(i));
			}
		}
	}
}

コードを実行してみましょう。


[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

最初はBobが頑張ってくれますが、途中でDianaが登場し、最終的にはFredが出てきます。

Fredでも解決できない301番以降は、429番だけを解決できるCharlieと、奇数番号を解決できるElmoが登場しているはずです。

このように連鎖的に処理を解決していくデザインパターンがChain Of Responsibilityパターンになります。

クラス図や登場人物の解説など、さらに詳しく解説を読みたい場合は書籍で確認してください。

関連するパターンや練習問題などもあるので、必ずスキルアップに繋がります。

まとめ

人間社会で「たらい回し」にされると腹が立ちますが、プログラミングの世界では便利な方法だったんですね。

ただし、前もって誰が解決するかが決まっているよりかは、たらい回しにしてしまう分、処理が遅くなってしまうのがデメリット。

Chain Of Responsibilityパターンが、どういったものかは理解できたので、使い所を見極めて活用していきたいと思います♪

いやここは違うだろ

と思った方はコメントいただけると嬉しいです。

最後までお読みいただきありがとうございました!

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です