Visitor (ビジター) パターン

Visitor とは訪問者という意味です。訪問者を受け入れ、訪問者に何かを処理してもらうデザインパターンです。
訪問者を受け入れるのは、Acceptor で、Acceptor は訪問者を受け入れますよ、という口を持っているだけで、
具体的な処理は、Visitor に委譲します。
実生活の例でいうと、宅配便の場合、宅配便業者のおじちゃんが家を訪問し、荷物を届けてくれて玄関で受け取ります。
この場合、宅配便業者のおじちゃんが Visitor であり、玄関がある家が Acceptor となります。
# 玄関の無い家は普通ないですが(^^;


サンプルでは、Composit パターンを流用し、
それぞれの階層を TreeView コントロールのノード選択をトリガにして訪問する事にしました。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1  サンプルの見た目2
アプリケーションを起動すると、画面上部に Composit パターン と同様の木構造が TreeView コントロールに表示されます。
任意のノードをクリックすると、選択ノード配下のチームやメンバー、更にその配下にあるチーム...というような、
配下の木構造を画面下部の TextBox コントロールに文字列で表現します。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント 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 TreeView1 As System.Windows.Forms.TreeView
  Friend WithEvents TextBox1 As System.Windows.Forms.TextBox
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
    Me.TreeView1 = New System.Windows.Forms.TreeView
    Me.TextBox1 = New System.Windows.Forms.TextBox
    Me.SuspendLayout()
    '
    'TreeView1
    '
    Me.TreeView1.ImageIndex = -1
    Me.TreeView1.Location = New System.Drawing.Point(8, 8)
    Me.TreeView1.Name = "TreeView1"
    Me.TreeView1.SelectedImageIndex = -1
    Me.TreeView1.TabIndex = 0
    '
    'TextBox1
    '
    Me.TextBox1.Location = New System.Drawing.Point(8, 112)
    Me.TextBox1.Name = "TextBox1"
    Me.TextBox1.TabIndex = 1
    Me.TextBox1.Text = "TextBox1"
    '
    'StartUpForm
    '
    Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12)
    Me.ClientSize = New System.Drawing.Size(292, 266)
    Me.Controls.Add(Me.TextBox1)
    Me.Controls.Add(Me.TreeView1)
    Me.Name = "StartUpForm"
    Me.Text = "Form1"
    Me.ResumeLayout(False)

  End Sub

#End Region

  'Root要素(幹)
  Private m_rootEntity As IEntity

  '画面がロードされた時のイベント
  Private Sub StartUpForm_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.Text = "Visitor"
    Me.TreeView1.Location = New System.Drawing.Point(8, 8)
    Me.TreeView1.Size = New System.Drawing.Size(270, 120)

    Me.TextBox1.Location = New System.Drawing.Point(8, 128)
    Me.TextBox1.Multiline = True
    Me.TextBox1.ScrollBars = ScrollBars.Vertical
    Me.TextBox1.Size = New System.Drawing.Size(270, 120)
    Me.TextBox1.Text = ""

    'CompositPerrternと同様にツリーを描画する。
    Me.DrawTreeNode()
  End Sub

  'TreeViewでノードが選択された時のイベント
  Private Sub TreeView1_AfterSelect(ByVal sender As Object, ByVal e As System.Windows.Forms.TreeViewEventArgs) _
                         Handles TreeView1.AfterSelect
    Dim currentNode As MyTreeNode = DirectCast(e.Node, MyTreeNode)
    Dim currentEntity As IEntity = currentNode.Entity
    '訪問者
    Dim myVisitor As ConcreteVisitor = New ConcreteVisitor

    '訪問する
    Me.TextBox1.Text = currentEntity.Accept(myVisitor)
  End Sub

  'TreeViewの描画
  Private Sub DrawTreeNode()

    Dim mdbPath As String = System.IO.Path.Combine(Application.StartupPath, "Composit.mdb")
    Dim connection_String As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + mdbPath + ";User Id=admin;Password=;"


    Dim myDa As CommonDataAccess.AbstructDataAccess = New CommonDataAccess.OleDataAccess
    Dim teamDt As DataTable
    Dim personDt As DataTable

    'DataBaseより値の取得
    Try
      myDa.Open(connection_String)
      teamDt = myDa.DataSelect("SELECT * FROM Team ORDER BY Parent_TeamID, Team_ID")
      personDt = myDa.DataSelect("SELECT * FROM Person ORDER BY Team_ID, Person_ID")
    Finally
      If Not myDa Is Nothing Then myDa.Close()
    End Try

    'Root要素(幹)を作成
    Me.m_rootEntity = New Team("Root", 0)

    '枝と葉っぱを追加する
    For Each tRow As DataRow In teamDt.Rows
      '枝を追加する
      Dim teamNode As IEntity
      Dim parentTeam_ID As Integer = CType(tRow("Parent_TeamID"), Integer)
      teamNode = New Team(DirectCast(tRow("Team_Name"), String), CType(tRow("Team_ID"), Integer))
      If parentTeam_ID = 0 Then
        Me.m_rootEntity.Add(teamNode)
      Else
        Me.serchTeamParent(parentTeam_ID).Add(teamNode)
      End If

      '葉っぱを追加する
      Dim rows() As DataRow = personDt.Select("Team_ID =" + teamNode.Id.ToString(), "Person_ID")
      For Each pRow As DataRow In rows
        Dim personNode As IEntity
        personNode = New Person(DirectCast(pRow("Person_Name"), String), CType(pRow("Person_ID"), Integer))
        teamNode.Add(personNode)
      Next
    Next

    'Compositパターンとの違いは
    'IEntityを保持できる様にカスタマイズしたMyTreeNodeを追加する事のみ
    Dim rootNode As MyTreeNode = New MyTreeNode(Me.m_rootEntity)
    Me.TreeView1.Nodes.Add(rootNode)
    For i As Integer = 0 To Me.m_rootEntity.Children.Length - 1
      Dim childEntity As IEntity = Me.m_rootEntity.Children(i)
      Me.SetTreeNode(childEntity, rootNode)
    Next

  End Sub

  'Parent_TeamIDをキーにして親要素を検索する
  Private Function serchTeamParent(ByVal parentId As Integer) As IEntity

    Dim hasChildren As Boolean = Me.m_rootEntity.HasChildren
    Dim currentEntity As IEntity = Me.m_rootEntity

    While (hasChildren)
      If currentEntity.Id = parentId Then Return currentEntity
      If currentEntity.HasChildren Then
        Dim children() As IEntity = currentEntity.Children
        For Each currentEntity In children
          If TypeOf currentEntity Is Team Then
            If currentEntity.Id = parentId Then
              Return currentEntity
            End If
            hasChildren = currentEntity.HasChildren
          End If
        Next
      End If
    End While

  End Function

  '再帰により、Rootノード配下の枝と葉っぱを描画する(枝と葉の同一視)
  Private Sub SetTreeNode(ByVal entity As IEntity, ByVal parentNode As TreeNode)

    Dim currentNode As MyTreeNode
    currentNode = New MyTreeNode(entity)
    parentNode.Nodes.Add(currentNode)

    If entity.HasChildren Then
      For Each childEntity As IEntity In entity.Children
        If Not childEntity.HasChildren Then
          currentNode.Nodes.Add(New MyTreeNode(childEntity))
        Else
          Me.SetTreeNode(childEntity, currentNode)
        End If
      Next
    End If
  End Sub
End Class
このカテゴリーの先頭へ このページの先頭へ

訪問者(Visitor)を表すインターフェースです。

IVisitor.vb
'訪問者インターフェース
Public Interface IVisitor

  'チームに対して訪問するメソッド定義
  Function Visit(ByVal myTeam As Team) As String
  '人に対して訪問するメソッド定義
  Function Visit(ByVal myPerson As Person) As String

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

具体的な訪問者です。
IVisitor を実装しています。

ConcreteVisitor.vb
'具体的な訪問者
Public Class ConcreteVisitor
  Implements IVisitor

  '人に対して訪問する
  Public Overloads Function Visit(ByVal myPerson As Person) As String Implements IVisitor.Visit
    Return " " + myPerson.Name + ControlChars.NewLine
  End Function

  'チームに対して訪問する
  Public Overloads Function Visit(ByVal myTeam As Team) As String Implements IVisitor.Visit
    Dim builder As System.Text.StringBuilder = New System.Text.StringBuilder

    With builder
      .Append(myTeam.Name + ControlChars.NewLine)
      If myTeam.HasChildren Then
        For Each child As IEntity In myTeam.Children
          .Append(" " + child.Accept(Me))
        Next
      End If
    End With

    Return builder.ToString()

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

訪問者を受け入れ可能である事を示すインターフェースです。

IAcceptor.vb

'訪問者対応インターフェース
Public Interface IAcceptor

  '訪問者を対応するメソッド定義
  Function Accept(ByVal v As IVisitor) As String

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

枝(チーム)または葉っぱ(メンバー)を表すインターフェースです。
このインターフェースを通して、枝または葉っぱを扱う事により、枝と葉っぱの同一視を実現できます。
Composit パターンと異なる点は、IAcceptor を継承している事です。

IEntity.vb
'訪問者受け入れ可能な枝or葉のインターフェース
Public Interface IEntity
  Inherits IAcceptor

  'エントリ(枝または葉っぱ)の名称を取得するプロパティ定義
  ReadOnly Property Name() As String
  'エントリ(枝または葉っぱ)のIDを取得するプロパティ定義
  ReadOnly Property Id() As Integer
  'エントリ(枝または葉っぱ)を追加するメソッド定義
  Sub Add(ByVal entity As IEntity)
  '子要素(枝または葉っぱ)があるかどうかを取得するプロパティ定義
  ReadOnly Property HasChildren() As Boolean
  '子要素(枝または葉っぱ)を取得するプロパティ定義
  ReadOnly Property Children() As IEntity()

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

訪問者受け入れ可能な枝(チーム)を表すクラスです。
IEntity を実装しています。

Team.vb
'チーム(枝)
Public Class Team
  Implements IEntity

  '名称
  Private m_name As String
  'ID
  Private m_id As Integer
  '子要素(枝または葉っぱ)
  Private m_childNodes As ArrayList

  'コンストラクタ
  Public Sub New(ByVal name As String, ByVal id As Integer)
    Me.m_name = name
    Me.m_id = id
    Me.m_childNodes = New ArrayList
  End Sub

  'エントリ(枝)の名称を取得するプロパティ実装
  Public ReadOnly Property Name() As String Implements IEntity.Name
    Get
      Return Me.m_name
    End Get
  End Property

  'エントリ(枝)のIDを取得するプロパティ実装
  Public ReadOnly Property Id() As Integer Implements IEntity.Id
    Get
      Return Me.m_id
    End Get
  End Property

  'エントリ(枝または葉っぱ)を追加するメソッド実装
  Public Sub Add(ByVal entity As IEntity) Implements IEntity.Add
    Me.m_childNodes.Add(entity)
  End Sub

  '子要素(枝または葉っぱ)があるかどうかを取得するプロパティ実装
  Public ReadOnly Property HasChildren() As Boolean Implements IEntity.HasChildren
    Get
      If Me.m_childNodes.Count > 0 Then
        Return True
      Else
        Return False
      End If
    End Get
  End Property

  '子要素(枝または葉っぱ)を取得するプロパティ実装
  Public ReadOnly Property Children() As IEntity() Implements IEntity.Children
    Get
      If Me.HasChildren Then
        Return DirectCast(Me.m_childNodes.ToArray(GetType(IEntity)), IEntity())
      Else
        Return Nothing
      End If
    End Get
  End Property

  '訪問者を対応する
  Public Function Accept(ByVal v As IVisitor) As String Implements IAcceptor.Accept
    Return v.Visit(Me)
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

訪問者受け入れ可能葉っぱ(メンバー)を表すクラスです。
IEntity を実装しています。

Person.vb
'人(葉っぱ)
Public Class Person
  Implements IEntity

  '名称
  Private m_name As String
  'ID
  Private m_id As Integer

  'コンストラクタ
  Public Sub New(ByVal name As String, ByVal id As Integer)
    Me.m_name = name
    Me.m_id = id
  End Sub

  'エントリ(葉っぱ)の名称を取得するプロパティ実装
  Public ReadOnly Property Name() As String Implements IEntity.Name
    Get
      Return Me.m_name
    End Get
  End Property

  'エントリ(葉っぱ)のIDを取得するプロパティ実装
  Public ReadOnly Property Id() As Integer Implements IEntity.Id
    Get
      Return Me.m_id
    End Get
  End Property

  'エントリ(枝または葉っぱ)を追加するメソッド実装
  Public Sub Add(ByVal entity As IEntity) Implements IEntity.Add
    'Personに対しての追加は不可(Parsonは常に葉っぱになる。枝にはならない)
    Throw New InvalidOperationException
  End Sub

  '子要素(枝または葉っぱ)があるかどうかを取得するプロパティ実装
  Public ReadOnly Property HasChildren() As Boolean Implements IEntity.HasChildren
    Get
      Return False '常にFalseを返却
    End Get
  End Property

  '子要素(枝または葉っぱ)を取得するプロパティ実装
  Public ReadOnly Property Children() As IEntity() Implements IEntity.Children
    Get
      Return Nothing '常にNothingを返却
    End Get
  End Property

  '訪問者を対応する
  Public Function Accept(ByVal v As IVisitor) As String Implements IAcceptor.Accept
    Return v.Visit(Me)
  End Function
End Class
このカテゴリーの先頭へ このページの先頭へ

現在の TreeNode に対応する IEntity オブジェクトを保持出来るように、
TreeNode クラスを継承してカスタマイズした、MyTreeNode です。

MyTreeNode.vb
'IEntityを保持できる様にカスタマイズしたMyTreeNode
Public Class MyTreeNode
  Inherits TreeNode

  '枝または葉っぱ
  Private m_Entity As IEntity

  'コンストラクタ
  Public Sub New(ByVal entity As IEntity)
    MyBase.New(entity.Name)
    Me.m_Entity = entity
  End Sub

  '枝または葉っぱのアクセサ
  Public ReadOnly Property Entity() As IEntity
    Get
      Return Me.m_Entity
    End Get
  End Property
End Class
このカテゴリーの先頭へ このページの先頭へ

■ひとこと

訪問者(Visitor)に具体的な処理を委譲する事で、訪問したときの処理の変更は、ConcreteVisitor クラスのみで吸収できます。
サンプルでは、Acceptor となるモノが Team クラスと Person クラスのみですが、
更に Acceptor の種類が増えたとしても柔軟な対応が可能です。


Acceptor と Visitor がお互いに相手のメソッドを呼び出していますが、
このような構造をダブルディスパッチと言います。

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