Visitor (ビジター) パターン
Visitor とは訪問者という意味です。訪問者を受け入れ、訪問者に何かを処理してもらうデザインパターンです。
訪問者を受け入れるのは、Acceptor で、Acceptor は訪問者を受け入れますよ、という口を持っているだけで、
具体的な処理は、Visitor に委譲します。
実生活の例でいうと、宅配便の場合、宅配便業者のおじちゃんが家を訪問し、荷物を届けてくれて玄関で受け取ります。
この場合、宅配便業者のおじちゃんが Visitor であり、玄関がある家が Acceptor となります。
# 玄関の無い家は普通ないですが(^^;
サンプルでは、Composit パターンを流用し、
それぞれの階層を TreeView コントロールのノード選択をトリガにして訪問する事にしました。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

アプリケーションを起動すると、画面上部に Composit パターン と同様の木構造が TreeView コントロールに表示されます。
任意のノードをクリックすると、選択ノード配下のチームやメンバー、更にその配下にあるチーム...というような、
配下の木構造を画面下部の TextBox コントロールに文字列で表現します。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント 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 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)を表すインターフェースです。
'訪問者インターフェース Public Interface IVisitor 'チームに対して訪問するメソッド定義 Function Visit(ByVal myTeam As Team) As String '人に対して訪問するメソッド定義 Function Visit(ByVal myPerson As Person) As String End Interface
具体的な訪問者です。
IVisitor を実装しています。
'具体的な訪問者 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
訪問者を受け入れ可能である事を示すインターフェースです。
'訪問者対応インターフェース Public Interface IAcceptor '訪問者を対応するメソッド定義 Function Accept(ByVal v As IVisitor) As String End Interface
枝(チーム)または葉っぱ(メンバー)を表すインターフェースです。
このインターフェースを通して、枝または葉っぱを扱う事により、枝と葉っぱの同一視を実現できます。
Composit パターンと異なる点は、IAcceptor を継承している事です。
'訪問者受け入れ可能な枝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 を実装しています。
'チーム(枝) 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 を実装しています。
'人(葉っぱ) 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 です。
'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 がお互いに相手のメソッドを呼び出していますが、
このような構造をダブルディスパッチと言います。