Composit (コンポジット) パターン
木構造になっているオブジェクト群をすっきりと表現し、扱いやすくするデザインパターンです。
木構造とは、その名の通り、木の幹と枝と葉っぱの関係です。
枝には枝が生える事もあり、葉っぱが生える事もあります。しかし、葉っぱから葉っぱや枝が生える事はありません。
Composit (コンポジット) パターンは、枝と葉っぱを同一視する事により、木構造を扱いやすい形で表現します。
サンプルでは、メンバーと所属するチームの関係を木構造で表現しました。
チームの中にサブチームやメンバーが存在する事はありますが、メンバーの中にチームが存在する事はない、という関係です。
■クラス図
ここで用いるサンプルのクラス図です。
準備中...
■サンプルの説明

ノード作成ボタンをクリックすると、内部でチームとメンバーの木構造を作成します。
描画ボタンをクリックすると、内部で作成された木構造を TreeView コントロールに描画します。
サンプルのプロジェクトダウンロード
■コード
アプリケーションのエントリポイント 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 Button1 As System.Windows.Forms.Button Friend WithEvents Button2 As System.Windows.Forms.Button Friend WithEvents TreeView1 As System.Windows.Forms.TreeView <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() Me.Button1 = New System.Windows.Forms.Button Me.Button2 = New System.Windows.Forms.Button Me.TreeView1 = New System.Windows.Forms.TreeView Me.SuspendLayout() ' 'Button1 ' Me.Button1.Location = New System.Drawing.Point(8, 8) Me.Button1.Name = "Button1" Me.Button1.TabIndex = 0 Me.Button1.Text = "Button1" ' 'Button2 ' Me.Button2.Location = New System.Drawing.Point(88, 8) Me.Button2.Name = "Button2" Me.Button2.TabIndex = 1 Me.Button2.Text = "Button2" ' 'TreeView1 ' Me.TreeView1.ImageIndex = -1 Me.TreeView1.Location = New System.Drawing.Point(8, 40) Me.TreeView1.Name = "TreeView1" Me.TreeView1.SelectedImageIndex = -1 Me.TreeView1.TabIndex = 2 ' 'StartUpForm ' Me.AutoScaleBaseSize = New System.Drawing.Size(5, 12) Me.ClientSize = New System.Drawing.Size(292, 266) Me.Controls.Add(Me.TreeView1) Me.Controls.Add(Me.Button2) Me.Controls.Add(Me.Button1) 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 = "Composit" Me.Button1.Text = "ノード作成" Me.Button2.Text = "描画" Me.Button2.Enabled = False Me.TreeView1.Size = New System.Drawing.Size(270, 210) Me.TreeView1.Nodes.Clear() End Sub 'ノード作成ボタンをクリックした時のイベント Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click 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(CType(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(CType(pRow("Person_Name"), String), CType(pRow("Person_ID"), Integer)) teamNode.Add(personNode) Next Next Me.Button2.Enabled = True 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 AndAlso _ TypeOf currentEntity Is Team Then Return currentEntity End If 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 '描画ボタンをクリックした時のイベント(枝と葉の同一視) Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click 'Rootノードを設定 Dim rootNode As TreeNode = New TreeNode(Me.m_rootEntity.Name) 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 '再帰により、Rootノード配下の枝と葉っぱを描画する(枝と葉の同一視) Private Sub SetTreeNode(ByVal entity As IEntity, ByVal parentNode As TreeNode) Dim currentNode As TreeNode currentNode = New TreeNode(entity.Name) 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 TreeNode(childEntity.Name)) Else Me.SetTreeNode(childEntity, currentNode) End If Next End If End Sub End Class
枝(チーム)または葉っぱ(メンバー)を表すインターフェースです。
このインターフェースを通して、枝または葉っぱを扱う事により、枝と葉っぱの同一視を実現できます。
'枝or葉 Public Interface IEntity 'エントリ(枝または葉っぱ)の名称を取得するプロパティ定義 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 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 End Class
■ひとこと
StartUpForm の中で、わざわざ木構造のオブジェクトを作成してから、TreeView コントロールに描画しているのは、
SetTreeNode メソッドの中で Composit パターンを用いて、どのような感じで枝と葉っぱを扱うのか見て頂きたかったからです。
枝の中に枝がある、というような再帰的な構造であっても、このようにシンプルに実装する事ができます。
Composit パターンの拡張版(?)に、Visitor パターンがあります。
掲載しているソースコードは、ほぼ同じような感じですので、是非目を通してみてください。