Menu
Coddy logo textTech

Compositeパターン

CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 96/107。

Composite パターンを使うと、個々のオブジェクトとオブジェクトのグループを同じように扱えます。State が内部状態に基づいて振る舞いを変えるのに対し、Composite は葉とコンテナの両方が同じインターフェースを共有するツリー構造を構築します。

このパターンは、ファイルシステム、組織図、UI components のような階層構造に最適です。Go では、個々の項目とコンテナの Both が implement する common interface を Define します。

type Component interface {
    GetSize() int
}

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

Composite(コンテナ)は子要素を保持し、それらの結果を集約することで同じinterfaceをimplementします。

type Folder struct {
    Name     string
    Children []Component
}

func (f *Folder) Add(c Component) {
    f.Children = append(f.Children, c)
}

func (f Folder) GetSize() int {
    total := 0
    for _, child := range f.Children {
        total += child.GetSize()
    }
    return total
}

これでフォルダーの中にフォルダーを入れ子にでき、GetSize() の呼び出しは、単一のファイルの場合でもディレクトリツリー全体の場合でも同じように機能します。

docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})

root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})

fmt.Println(root.GetSize())  // 175

Compositeは、部分と全体の階層構造を表現する必要があり、クライアントが個々のオブジェクトと合成オブジェクトを同一に扱えるようにしたい場合に最適です。

challenge icon

チャレンジ

簡単

Composite パターンを使って組織図システムを構築しましょう!個々の employees と、employees や他の departments を contain する departments の両方を統一的に扱える構造を作成します。組織内の任意の部分の合計給与を計算するのに最適です。

コードを3つのファイルに分けて整理します。

  • component.go:個々の要素とグループの両方が implement する common interface を Define します。

    2つのメソッドを持つ OrgComponent interface を Create します。

    • GetSalary() int:この component の合計給与を返します
    • GetName() string:employee または department の名前を返します
  • organization.go:leaf(個々の employee)型と composite(department)型の両方を Implement します。

    Name(string)フィールドと Salary(int)フィールドを持つ Employee struct を Create します。その GetSalary() は自身の給与を返し、GetName() は自身の名前を返します。

    Name(string)フィールドと、OrgComponent items を保持する Members slice を持つ Department struct を Create します。members を追加するための Add(c OrgComponent) method を Add します。その GetSalary() はすべての members の給与の合計を返し、GetName() は department の名前を返すようにします。

  • main.go:organization structure を構築し、給与を計算します。

    employees の数を読み取ります。各 employee について、名前と給与を読み取り、Employee instances を Create します。次に departments の数を読み取ります。各 department について、名前と member indices の count を読み取り、その後に indices を読み取ります(0-based で、creation の順序における、以前に Create された employees または departments を参照します)。

    structure の構築後、final index を読み取り、その component の名前と合計給与を次の format で出力します:[name]: [salary]

次の入力が提供されます。

  • employees の数、その後に各 employee の名前と給与を別々の行で指定
  • departments の数、その後に各 department の名前、member count、member indices を指定
  • query 対象の final index

たとえば、次の入力の場合:

3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3

出力は次のようになります。

Engineering: 165000

また、次の入力の場合:

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

出力は次のようになります。

QA: 125000

また、次の入力の場合:

2
Alice
50000
Bob
60000
0
1

出力は次のようになります。

Bob: 60000

GetSalary() は、単一の employee に対して呼び出す場合でも department 全体に対して呼び出す場合でも同じように動作することに注目してください。Composite パターンにより、同じ interface を通じて個々の要素とグループを統一的に扱えます。

自分で試してみよう

package main

import "fmt"

func main() {
	// 従業員数を読み取る
	var numEmployees int
	fmt.Scanln(&numEmployees)

	// すべてのコンポーネント(従業員と部署)を作成順に格納する
	var components []OrgComponent

	// TODO: 各従業員の名前と給与を読み取る
	// Employeeインスタンスを作成し、componentsスライスに追加する
	for i := 0; i < numEmployees; i++ {
		var name string
		var salary int
		fmt.Scanln(&name)
		fmt.Scanln(&salary)
		// TODO: 従業員を作成してcomponentsに追加する
	}

	// 部署数を読み取る
	var numDepartments int
	fmt.Scanln(&numDepartments)

	// TODO: 各部署の名前、メンバー数、メンバーのインデックスを読み取る
	// Departmentインスタンスを作成し、インデックスでメンバーを追加し、componentsに追加する
	for i := 0; i < numDepartments; i++ {
		var deptName string
		var memberCount int
		fmt.Scanln(&deptName)
		fmt.Scanln(&memberCount)
		// TODO: 部署を作成し、メンバーのインデックスを読み取り、メンバーを追加し、componentsに追加する
	}

	// 照会する最終インデックスを読み取る
	var queryIndex int
	fmt.Scanln(&queryIndex)

	// TODO: コンポーネントの名前と合計給与を次の形式で出力する: [name]: [salary]
	// 例: fmt.Printf("%s: %d\n", name, salary)
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ