GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Composite」を学ぶ"
今回は、Pythonで実装した”Composite”のサンプルアプリをGolangで実装し直してみました。
■ Composite(コンポジット・パターン)
Compositeパターン(コンポジット・パターン)とは、GoF (Gang of Four; 4人のギャングたち) によって定義された デザインパターンの1つである。「構造に関するパターン」に属する。Compositeパターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。
UML class and object diagram
UML class diagram
□ 備忘録
書籍「増補改訂版Java言語で学ぶデザインパターン入門」の引用ですが、腹落ちしました。
ディレクトリの中には、ファイルが入っていたり、別のディレクトリ(サブディレクトリ)が入っていたりします。そしてまた、そのサブディレクトリの中には他のファイルやサブディレクトリが入っていることもあります。
ディレクトリは、そのような「入れ子」になった構造、再帰的な構造を作り出しています。
... (snip)
Compositeパターンは、このような構造を作るためのものであり、容器と中身を同一視し、再帰的な構造を作るデザインパターン
■ "Composite"のサンプルプログラム
実際に、Compositeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- ルートエントリの
ディレクトリ
に、サブディレクトリ
およびファイル
を追加してみる - ルートエントリの
ディレクトリ
に、ユーザエントリのディレクトリ
を追加して、さらに、サブディレクトリ
およびファイル
を追加してみる - 敢えて、
ファイル
に、ディレクトリ
を追加して、失敗することを確認する
$ go run Main.go
Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)
Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/Composite.java (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)
Occurring Exception...
FileTreatmentException
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Composite
- ディレクトリ構成
.
├── Main.go
└── composite
├── directory.go
├── entry.go
└── file.go
(1) Leaf(葉)の役
「中身」を表す役です。この役の中には、他のものを入れることができません。
サンプルプログラムでは、File
構造体が、この役を努めます。
package composite
import (
"fmt"
)
// File is sturct
type File struct {
name string
size int
}
// NewFile func for initializing File
func NewFile(name string, size int) *File {
return &File{
name: name,
size: size,
}
}
func (f *File) getName() string {
return f.name
}
func (f *File) getSize() int {
return f.size
}
// Add func for adding file
func (f *File) Add(entry entry) {
if err := doError(); err != nil {
fmt.Println(err)
}
}
// PrintList func for printing directory name
func (f *File) PrintList(prefix string) {
f.print(prefix)
}
func (f *File) print(prefix string) {
fmt.Printf("%s/%s (%d)\n", prefix, f.getName(), f.getSize())
}
func doError() error {
msg := "FileTreatmentException"
return fmt.Errorf("%s", msg)
}
(2) Composite(複合体)の役
「容器」を表す役です。Leaf
役や、Composite
役を入れることができます。
サンプルプログラムでは、Directory
構造体が、この役を努めます。
package composite
import "fmt"
// Directory is sturct
type Directory struct {
name string
directory []entry
}
// NewDirectory func for initializing Directory
func NewDirectory(name string) *Directory {
return &Directory{
name: name,
}
}
func (d *Directory) getName() string {
return d.name
}
func (d *Directory) getSize() int {
size := 0
for _, entry := range d.directory {
size += entry.getSize()
}
return size
}
// Add func for adding directory
func (d *Directory) Add(entry entry) {
d.directory = append(d.directory, entry)
}
// PrintList func for printing directory name
func (d *Directory) PrintList(prefix string) {
d.print(prefix)
for _, e := range d.directory {
e.PrintList(prefix + "/" + d.name)
}
}
func (d *Directory) print(prefix string) {
fmt.Printf("%s/%s (%d)\n", prefix, d.getName(), d.getSize())
}
(3) Componentの役
Leaf
役とComposite
役を同一視するための役です。
サンプルプログラムでは、Entry
インタフェースが、この役を努めます。
package composite
type entry interface {
PrintList(prefix string)
getSize() int
}
(4) Client(依頼人)の役
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
"fmt"
"./composite"
)
func startMain() {
fmt.Println("Making root entries...")
rootdir := composite.NewDirectory("root")
bindir := composite.NewDirectory("bin")
tmpdir := composite.NewDirectory("tmp")
usrdir := composite.NewDirectory("usr")
rootdir.Add(bindir)
rootdir.Add(tmpdir)
rootdir.Add(usrdir)
bindir.Add(composite.NewFile("vi", 10000))
bindir.Add(composite.NewFile("latex", 20000))
rootdir.PrintList("")
fmt.Println("")
fmt.Println("Making user entries...")
yuki := composite.NewDirectory("yuki")
hanako := composite.NewDirectory("hanako")
tomura := composite.NewDirectory("tomura")
usrdir.Add(yuki)
usrdir.Add(hanako)
usrdir.Add(tomura)
yuki.Add(composite.NewFile("diary.html", 100))
yuki.Add(composite.NewFile("Composite.java", 200))
hanako.Add(composite.NewFile("memo.tex", 300))
tomura.Add(composite.NewFile("game.doc", 400))
tomura.Add(composite.NewFile("junk.mail", 500))
rootdir.PrintList("")
fmt.Println("")
fmt.Println("Occurring Exception...")
tmpfile := composite.NewFile("junk.mail", 500)
bindir = composite.NewDirectory("bin")
tmpfile.Add(bindir)
}
func main() {
startMain()
}
(5) その他
エラー時の振る舞いを追加します
func doError() error {
msg := "FileTreatmentException"
return fmt.Errorf("%s", msg)
}