Chain Of Responsibility (チェイン オブ レスポンシビリティ) パターン

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


サンプルでは、"駄目だし"の役(チェック役)をたらいまわしします。
上の例で言うと、「ここは○○電気ではありませんが」や「奥さん、コンセント抜けてませんか」が例外として投げられるわけです。

このカテゴリーの先頭へ このページの先頭へ

■クラス図

ここで用いるサンプルのクラス図です。
準備中...

このカテゴリーの先頭へ このページの先頭へ

■サンプルの説明

サンプルの見た目1
TextBox には "3 〜 10 バイト" の "半角英字" を "必ず" 入力しなければならない仕様とします。
チェックボタンをクリックすると、上記条件に合致しない文字列が入力された場合、エラーメッセージを表示します。
サンプルのプロジェクトダウンロード

このカテゴリーの先頭へ このページの先頭へ

■コード

アプリケーションのエントリポイント StartUpForm です。

StartUpForm.vb
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
このカテゴリーの先頭へ このページの先頭へ

チェック役の親クラスです。
同時に、サブクラスのこまごまとしたチェッカー達を集約するクラスでもあります。

AbstractChecker.vb
'抽象 チェック
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 を継承しています。

BlankCheck.vb
'ブランク チェック
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 を継承しています。

ByteCheck.vb
'バイト数 チェック
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 を継承しています。

AlphabetCheck.vb
'半角英字 チェック
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 メソッドを呼び出すことにより、
たらいまわしのチェーンを作成しています。
こうする事によって、柔軟に「このチェックは要る、このチェックは要らない」に対応する事が出来ます。


しかし...実際に実行された方はお解かりかと思いますが、
毎回この"たらいまわし"のチェーンを通る為、エラーメッセージがあがるまで"もっさり"しています。
速度を優先する場合はこのパターンはちょっと向いていないかも知れません。
ただし、柔軟性においては優れているんじゃないかな〜と思います。

このカテゴリーの先頭へ このページの先頭へ