Compositeパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 96/107。
Composite パターンを使用すると、個々のオブジェクトとオブジェクトのグループを一貫して扱うことができます。State パターンが内部状態に基づいて振る舞いを変えるのに対し、Composite は、末端(リーフ)とコンテナの両方が同じインターフェースを共有するツリー構造を構築します。
このパターンは、ファイルシステム、組織図、UIコンポーネントのような階層構造に最適です。Goでは、個々のアイテムとコンテナの両方が実装する共通のインターフェースを定義します:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}コンポジット(コンテナ)は子要素を保持し、それらの結果を集約することで同じインターフェースを実装します:
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 パターンを使用して、組織図システムを構築しましょう!個々の従業員と、従業員や他の部門を含む部門の両方を一律に扱うことができる構造を作成します。これは、組織のあらゆる部分で合計給与を計算するのに最適です。
コードは 3 つのファイルに分けて構成します:
component.go: 個人とグループの両方が実装する共通のインターフェースを定義します。2 つのメソッドを持つ
OrgComponentインターフェースを作成します:GetSalary() int— このコンポーネントの合計給与を返しますGetName() string— 従業員または部門の名前を返します
organization.go: リーフ(個々の従業員)とコンポジット(部門)の両方の型を実装します。Name(string) とSalary(int) フィールドを持つEmployee構造体を作成します。そのGetSalary()は自身の給与を返し、GetName()は自身の名前を返します。Name(string) フィールドと、OrgComponentアイテムを保持するMembersスライスを持つDepartment構造体を作成します。メンバーを追加するためのAdd(c OrgComponent)メソッドを追加します。そのGetSalary()はすべてのメンバーの給与の合計を返し、GetName()は部門名を返します。main.go: 組織構造を構築し、給与を計算します。従業員の数を読み取ります。各従業員について、名前と給与を読み取り、
Employeeインスタンスを作成します。次に、部門の数を読み取ります。各部門について、その名前とメンバーのインデックス数、続いてそれらのインデックス(作成順に、以前に作成された従業員または部門を指す 0 から始まる数値)を読み取ります。構造を構築した後、最後のインデックスを読み取り、そのコンポーネントの名前と合計給与を
[name]: [salary]の形式で出力します。
以下の入力が提供されます:
- 従業員数、続いて各従業員の名前と給与(別々の行)
- 部門数、続いて各部門の名前、メンバー数、およびメンバーのインデックス
- クエリする最終インデックス
例えば、以下の場合:
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() が、単一の従業員に対して呼び出すか、部門全体に対して呼び出すかにかかわらず、同じように機能することに注目してください。Composite パターンを使用すると、同じインターフェースを通じて個人とグループを一律に扱うことができます!
チートシート
Composite パターンは、共通のインターフェースを通じて、個々のオブジェクトとオブジェクトのグループを一貫して扱います。ファイルシステム、組織図、UIコンポーネントのような階層構造に最適です。
個々のアイテムとコンテナの両方が実装する共通のインターフェースを定義します:
type Component interface {
GetSize() int
}リーフ(個々のアイテム)を実装します:
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}子要素を保持し、その結果を集計するコンポジット(コンテナ)を実装します:
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
}個々のアイテムとコンポジットを一貫して扱う使用例:
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自分で試してみよう
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