GoFデザインパターン ~Compositeパターン~
Compositeパターンとは
ファイルシステムのような、中身(ファイル)とその入れ子(ディレクトリ)を再帰的に処理するためのパターン。容器と中身を同一視し、再帰的な構造を作る。
Compositeパターンにおける役割
name | description |
---|---|
Leaf | 容器の中身を表すもの。ファイルシステムの例ではファイルがこの役割。 |
Composite | 容器を表すもの。ファイルシステムの例ではディレクトリ。 |
Component | LeafとCompositeを同一視するためのもの。今回書いたサンプルコードにおけるEntryインターフェースがこれ。 |
Client | Compositeパターンの利用者。 |
コード例
Entryインターフェース
package main type Entry interface { GetName() string GetSize() int64 Add(Entry) error }
ファイルとディレクトリを同一のものとして扱えるようにするためのインターフェース。ファイルクラスとディレクトリクラスがそれぞれこのインターフェースを実装するが、この方法のデメリットは、ファイルクラスでもAdd(Entry) error
のようなディレクトリクラスのためのメソッドを実装しなくてはいけなくなる(ファイルクラスにおけるAdd(Entry) error
はただエラーを返すためだけのメソッド)。
File
package main import "github.com/pkg/errors" type file struct { name string size int64 } func NewFile(name string, size int64) Entry { return &file{ name: name, size: size, } } func (f *file) GetName() string { return f.name } func (f *file) GetSize() int64 { return f.size } func (f *file) Add(Entry) error { return errors.New("This is not directory") }
ファイルを表す構造体。Add()
はディレクトリにおいてのみ必要だが、Entryインターフェースを満たすためにここでも実装する
Directory
package main type directory struct { name string size int64 entries []Entry } func NewDirectory(name string, size int64) Entry { return &directory{ name: name, size: size, } } func (d *directory) GetName() string { return d.name } func (d *directory) GetSize() int64 { return d.size } func (d *directory) Add(entry Entry) error { d.entries = append(d.entries, entry) return nil }
ディレクトリを表す構造体。
Client
package main import ( "flag" "fmt" "io/ioutil" "os" "path/filepath" "strings" ) func main() { os.Exit(run()) } func run() int { flag.Parse() for _, v := range walk(getPath()) { fmt.Printf("%#v", v) } return 0 } func walk(path string) []Entry { files, err := ioutil.ReadDir(path) if err != nil { panic(err) } var entries []Entry for _, file := range files { var entry Entry if file.IsDir() { entry := NewDirectory(file.Name(), file.Size()) entries = append(entries, walk(filepath.Join(path, file.Name()))...) for _, entryInDir := range entries { err := entry.Add(entryInDir) if err != nil { panic("panic!") } } } else { entry = NewFile(file.Name(), file.Size()) } entries = append(entries, entry) } return entries } func getPath() string { var path string if args := flag.Args(); len(args) > 0 { path = args[0] if !strings.HasSuffix(path, "/") { path = path + "/" } } else { path = "./" } return path }
Compositeを利用するClientの実装。walk()
はディレクトリを探索するための関数で、ディレクトリに対しては walk()
の中で walk()
を再帰的に呼び出す。