Chain Of Responsibility (チェイン オブ レスポンシビリティ) パターン
何かの処理を自分でできなければ、次、その人もできなければまた次...と、たらい回すデザインパターンです。
実生活では、あまりよい気分のたとえではありませんが、どこかのサポートセンター等に何かの電化製品について故障の問い合わせをしたとしましょう。
電話をかけると、
(1) まず受付の人が電話を取ります。
(2) その次に商品の部署へ電話を転送します。
(3) とりあえず電話に出た人は、専門的な話であれば、詳しい人に電話を転送します。
(4) 部品の交換になったら、出張サービスの担当さんが部品交換に来る日時等を決める為に電話を転送します。
例えば、電話の掛け間違えであれば (1) の段階で、「ここは○○電気ではありませんが」「すみません間違えました...」でおしまいです。
電話番号は OK であって、うっかりしていたのだとしたら、(2) の段階で「奥さん、コンセント抜けてませんか」「あらやだ」で一件落着。
本当に壊れていたら、(4) で、担当さんに話して明日交換にしに来てもらいます。
サンプルでは、"駄目だし"の役(チェック役)をたらいまわしします。
上の例で言うと、「ここは○○電気ではありませんが」や「奥さん、コンセント抜けてませんか」が例外として投げられるわけです。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

TextBox には "3 〜 10 バイト" の "半角英字" を "必ず" 入力しなければならない仕様とします。
チェックボタンをクリックすると、上記条件に合致しない文字列が入力された場合、エラーメッセージを表示します。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント StartUpForm です。
Public Class StartUpForm Inherits System.Windows.Forms.Form #Region " Windows フォーム デザイナで生成されたコード " Public Sub New() MyBase.New() ' この呼び出しは Windows フォーム デザイナで必要です。 InitializeComponent() ' InitializeComponent() 呼び出しの後に初期化を追加します。 AddHandler Application.ThreadException, AddressOf CommonExceptionAndHandler.ExceptionHandler.MyHandler End Sub ' Form は、コンポーネント一覧に後処理を実行するために dispose をオーバーライドします。 Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub ' Windows フォーム デザイナで必要です。 Private components As System.ComponentModel.IContainer ' メモ : 以下のプロシージャは、Windows フォーム デザイナで必要です。 'Windows フォーム デザイナを使って変更してください。 ' コード エディタを使って変更しないでください。 Friend WithEvents TextBox1 As System.Windows.Forms.TextBox Friend WithEvents Button1 As System.Windows.Forms.Button <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.TextBox1 = New System.Windows.Forms.TextBox Me.Button1 = New System.Windows.Forms.Button Me.SuspendLayout() ' 'TextBox1 ' Me.TextBox1.Location = New System.Drawing.Point(56, 64) Me.TextBox1.Name = "TextBox1" Me.TextBox1.TabIndex = 0 Me.TextBox1.Text = "TextBox1" ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(168, 64) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 1 Me.Button1.Text = "Button1" ' 'StartUpForm ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12) Me.ClientSize = New System.Drawing.Size(292, 266) Me.Controls.Add(Me.Button1) Me.Controls.Add(Me.TextBox1) Me.Name = "StartUpForm" Me.Text = "Form1" Me.ResumeLayout(False) End Sub #End Region '入力データの項目名 Private Const FIELD_NAME As String = "てすと項目名" '1 番目のチェッカー(ブランク チェック) Private m_rootChecker As AbstractChecker '2 番目のチェッカー(バイト数 チェック) Private m_byteChecker As AbstractChecker '3 番目のチェッカー(アルファベット チェック) Private m_alphabetChecker As AbstractChecker '画面がロードされた時のイベント Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load Me.Text = "Chain of Responsibility" Me.TextBox1.Text = "" Me.Button1.Text = "チェック" 'チェッカーを作成 Me.m_rootChecker = New BlankCheck(FIELD_NAME) Me.m_byteChecker = New ByteCheck(FIELD_NAME, 3, 10) Me.m_alphabetChecker = New AlphabetCheck(FIELD_NAME) 'チェーンを作成 Me.m_rootChecker.SetNext(Me.m_byteChecker).SetNext(Me.m_alphabetChecker) End Sub 'チェックボタンをクリックした時のイベント Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) _ Me.m_rootChecker.Check(Me.TextBox1.Text) MessageBox.Show("チェックが終わりました", _ Me.Text, _ MessageBoxButtons.OK, _ MessageBoxIcon.Information) End Sub End Class
チェック役の親クラスです。
同時に、サブクラスのこまごまとしたチェッカー達を集約するクラスでもあります。
'抽象 チェック Public MustInherit Class AbstractChecker '入力データの項目名(メッセージ表示用) Private m_inputDataName As String '次のチェッカー Private m_nextCheck As AbstractChecker 'コンストラクタ Public Sub New(ByVal inputDataName As String) Me.m_inputDataName = inputDataName End Sub 'チェックをたらい回す相手を設定 Public Function SetNext(ByVal inputDataCheck As AbstractChecker) As AbstractChecker Me.m_nextCheck = inputDataCheck Return inputDataCheck End Function 'チェック Public Sub Check(ByVal inputData As String) Dim checkResult As Boolean = Me.ConcreteCheckMethod(inputData) If checkResult AndAlso Not m_nextCheck Is Nothing Then '次のチェックへたらい回し Me.m_nextCheck.Check(inputData) ElseIf checkResult AndAlso m_nextCheck Is Nothing Then 'チェック終了 Console.WriteLine("チェックおしまい") Else Throw New CommonExceptionAndHandler.MyException(inputData + _ "[" + Me.m_inputDataName + "]は" + _ Me.GetType().Name + "でNGが返されました") End If End Sub '実際にチェックするメソッド定義 Protected MustOverride Function ConcreteCheckMethod(ByVal inputData As String) As Boolean End Class
ブランクかどうかチェックする役です。
AbstractChecker を継承しています。
'ブランク チェック Public Class BlankCheck Inherits AbstractChecker 'コンストラクタ Public Sub New(ByVal inputDataName As String) MyBase.New(inputDataName) End Sub '実際にチェックするメソッド実装 Protected Overrides Function ConcreteCheckMethod(ByVal inputData As String) As Boolean If inputData = "" Then Return False End If Return True End Function End Class
指定バイト数以上、指定バイト数以下かどうかをチェックする役です。
AbstractChecker を継承しています。
'バイト数 チェック Public Class ByteCheck Inherits AbstractChecker '最大バイト数 Private m_min As Integer '最小バイト数 Private m_max As Integer 'コンストラクタ Public Sub New(ByVal inputDataName As String, ByVal min As Integer, ByVal max As Integer) MyBase.New(inputDataName) Me.m_min = min Me.m_max = max End Sub '実際にチェックするメソッド実装 Protected Overrides Function ConcreteCheckMethod(ByVal inputData As String) As Boolean Dim byteCount As Integer = System.Text.Encoding.Default.GetByteCount(inputData) If byteCount < Me.m_min OrElse Me.m_max < byteCount Then Return False End If Return True End Function End Class
半角英字かどうかをチェックする役です。
AbstractChecker を継承しています。
'半角英字 チェック Public Class AlphabetCheck Inherits AbstractChecker 'コンストラクタ Public Sub New(ByVal inputDataName As String) MyBase.New(inputDataName) End Sub '実際にチェックするメソッド実装 Protected Overrides Function ConcreteCheckMethod(ByVal inputData As String) As Boolean Return System.Text.RegularExpressions.Regex.IsMatch(inputData, "^[a-zA-Z]+$") End Function End Class
■ひとこと
StartUpForm の中で、チェックする役達の SetNext メソッドを呼び出すことにより、
たらいまわしのチェーンを作成しています。
こうする事によって、柔軟に「このチェックは要る、このチェックは要らない」に対応する事が出来ます。
しかし...実際に実行された方はお解かりかと思いますが、
毎回この"たらいまわし"のチェーンを通る為、エラーメッセージがあがるまで"もっさり"しています。
速度を優先する場合はこのパターンはちょっと向いていないかも知れません。
ただし、柔軟性においては優れているんじゃないかな〜と思います。