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()) // 175Compositeは、部分と全体の階層構造を表現する必要があり、クライアントが個々のオブジェクトと合成オブジェクトを同一に扱えるようにしたい場合に最適です。
チャレンジ
簡単Composite パターンを使って組織図システムを構築しましょう!個々の employees と、employees や他の departments を contain する departments の両方を統一的に扱える構造を作成します。組織内の任意の部分の合計給与を計算するのに最適です。
コードを3つのファイルに分けて整理します。
component.go:個々の要素とグループの両方が implement する common interface を Define します。2つのメソッドを持つ
OrgComponentinterface を Create します。GetSalary() int:この component の合計給与を返しますGetName() string:employee または department の名前を返します
organization.go:leaf(個々の employee)型と composite(department)型の両方を Implement します。Name(string)フィールドとSalary(int)フィールドを持つEmployeestruct を Create します。そのGetSalary()は自身の給与を返し、GetName()は自身の名前を返します。Name(string)フィールドと、OrgComponentitems を保持するMembersslice を持つDepartmentstruct を Create します。members を追加するためのAdd(c OrgComponent)method を Add します。そのGetSalary()はすべての members の給与の合計を返し、GetName()は department の名前を返すようにします。main.go:organization structure を構築し、給与を計算します。employees の数を読み取ります。各 employee について、名前と給与を読み取り、
Employeeinstances を 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: 60000GetSalary() は、単一の 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)
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware自分で練習してみよう: Goオンラインコンパイラ