Composit (コンポジット) パターン

木構造になっているオブジェクト群をすっきりと表現し、扱いやすくするデザインパターンです。
木構造とは、その名の通り、木の幹と枝と葉っぱの関係です。
枝には枝が生える事もあり、葉っぱが生える事もあります。しかし、葉っぱから葉っぱや枝が生える事はありません。
Composit (コンポジット) パターンは、枝と葉っぱを同一視する事により、木構造を扱いやすい形で表現します。


サンプルでは、メンバーと所属するチームの関係を木構造で表現しました。
チームの中にサブチームやメンバーが存在する事はありますが、メンバーの中にチームが存在する事はない、という関係です。

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

■クラス図

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

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

■サンプルの説明

サンプルの見た目1  サンプルの見た目2
ノード作成ボタンをクリックすると、内部でチームとメンバーの木構造を作成します。
描画ボタンをクリックすると、内部で作成された木構造を TreeView コントロールに描画します。
サンプルのプロジェクトダウンロード

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

■コード

アプリケーションのエントリポイント 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 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
このカテゴリーの先頭へ このページの先頭へ

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

IEntity.vb
'枝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 を実装しています。

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

■ひとこと

StartUpForm の中で、わざわざ木構造のオブジェクトを作成してから、TreeView コントロールに描画しているのは、
SetTreeNode メソッドの中で Composit パターンを用いて、どのような感じで枝と葉っぱを扱うのか見て頂きたかったからです。
枝の中に枝がある、というような再帰的な構造であっても、このようにシンプルに実装する事ができます。


Composit パターンの拡張版(?)に、Visitor パターンがあります。
掲載しているソースコードは、ほぼ同じような感じですので、是非目を通してみてください。

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