Goでデザインパターン (Compositeパターン)

Goデザインパターン

Compositeパターンとは

構造に関するデザインパターンの一つ。単数オブジェクト(Leaf)と複数オブジェクト(Composite)を同一視することで、再帰的な構造(ツリー構造)を実現する。

長所

  • 再帰を利用して複雑なツリー構造を便利に扱うことができる。
  • 既存のコードに手を入れることなく、別のオブジェクトツリーと新規の要素を追加できる。

短所

  • 機能が大きく異なるクラスをまとめる場合、共通インターフェースを宣言が難しい場合がある。
    場合によっては過度な一般化により理解困難になる可能性。

利用場面

  • ツリーのような構造を実装する場合。
  • クライアント側処理で単純な要素と複雑な要素を同一のものとして扱えるようにしたい場合。

クラス図

  • Component
    Leaf, Compositeに共通するAPIを宣言する。
  • Leaf
    Compositeパターンで表現されるツリーの基本要素。子要素を持たない。
    Leafは仕事の委譲先がないため、実際の作業のほとんどはここで実装される。
  • Composite (別名: Container)
    LeafやCompositeを子要素として持つ要素。子要素のとやり取りはComponentインターフェースで宣言されているAPIを通じて行われる。つまり、子要素の具象については認知しない。
    実際の仕事を子に委任し、結果をまとめ上げる役割を果たす。
  • Client
    Componentで宣言されたAPIを通じてやり取りを行う。

実装

Component

package main

// Leaf, Compositeに共通するインターフェースを宣言
type Component interface {
	show(indent int)
  // 場合によってはここで要素追加用メソッドを宣言する場合もある。
  // その場合、クライアント側で全ての要素を同等に扱える利点がある。
  // add(c Component)
}

Leaf

package main

import (
	"fmt"
	"strings"
)

// ツリーの末端オブジェクトを表現する。
// 基本的に実際の作業はここで定義される。
type Row struct {
	values []string
}

func NewRow(s ...string) *Row {
	return &Row{values: s}
}

func (r *Row) show(indent int) {
	fmt.Printf("%s%v\n", strings.Repeat("--", indent), r.values)
}

// Componentで宣言されている場合は実装する必要あり。
// 何も行わないか、エラーを返すようにする。
// func (r *Row) add(c Component) {}

Composite

package main

import (
	"fmt"
	"strings"
)

// 子要素を持つことができる複雑なComponentを表現する
type Table struct {
	components []Component
	values []string
}

func NewTable(s ...string) *Table {
	return &Table{values: s, components: []Component{}}
}

func (t *Table) show(indent int) {
	fmt.Printf("%s%v\n", strings.Repeat("--", indent), t.values)
	for _, composite := range t.components {
		composite.show(indent + 1)
	}
}

func (t *Table) add(c ...Component) {
	t.components = append(t.components, c...)
}

動作確認

package main

func main() {
	table1 := NewTable([]string{"名前", "年齢", "出身地"}...)
	row1 := NewRow([]string{"田中", "22歳", "東京"}...)

	table2 := NewTable([]string{"佐藤", "54歳", "北海道"}...)
	row3 := NewRow([]string{"168cm", "59kg", "A型"}...)

	table3 := NewTable([]string{"北村", "20歳", "千葉"}...)
	row4 := NewRow([]string{"189cm", "90kg", "O型"}...)
	row5 := NewRow([]string{"会社員", "未婚"}...)
	table4 := NewTable("銀行員として勤務中。趣味は登山。")
	row6 := NewRow("明日は登山会の仲間と富士山に登山予定。")

	table2.add(row3)
	table4.add(row6)
	table3.add(row4, row5, table4)
	table1.add(row1, table2, table3)
	
	table1.show(0)
}
>> go run .
[名前 年齢 出身地]
--[田中 22歳 東京]
--[佐藤 54歳 北海道]
----[168cm 59kg A型]
--[北村 20歳 千葉]
----[189cm 90kg O型]
----[会社員 未婚]
----[銀行員として勤務中。趣味は登山。]
------[明日は登山会の仲間と富士山に登山予定。]

補足

  • 上記実装例ではComponentインターフェースでは子要素追加用メソッドは宣言していないが、インターフェースに含める場合もある。その場合はLeafではエラーを返したり、何も行わないように実装する必要がある。しかし、クライアント側でツリー作成時に全要素を同等に扱えることを保証できる

コメント

タイトルとURLをコピーしました