こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるCompositeパターンについて紹介します。
<目次>
デザインパターンまとめ
シチュエーション
Compositeパターンは、構造体に関するパターンで、再帰処理を実装したい場合に有効です。
例えば、以下のディレクトリがあるとします。
1 |
CompositeDir |
このフォルダ配下に、サブフォルダやファイルを作成したいとします。
1 2 3 4 5 6 7 8 9 10 11 |
CompositeDir |── Sample1 ├── Sample2 │ ├── c.go │ └── d.go ├── Sample3 │ ├── e.go │ └── f.go ├── a.go └── b.go |
CompositeDirの配下にSample1、そして、その配下にSample2,3が続きます。また、各ディレクトリには、goファイルが格納される感じです。
これらは、ディレクトリ → ファイル、ディレクトリ → ファイル、ディレクトリ → ファイルと作成していきます。
1 2 3 4 5 6 7 8 9 |
A-1. Sample1 作成 |-> B-1. Sample2 作成 │ ├─> B-2. c.go 作成 │ └─> B-3. d.go 作成 ├─> C-1. Sample3 作成 │ ├─> C-2. e.go 作成 │ └─> C-3. f.go 作成 ├─> A-2. a.go 作成 └─> A-3. b.go 作成 |
この処理は、「自分自身を呼び出す処理が書かれている関数(再帰処理)」を呼び出すことで実現することができます。
Compositeパターン:Interface無し
Compositeパターンで、プログラミングしていきましょう。
Interface有り無しはぶっちゃけあまり関係なく、再帰処理の方に注目してください。
容器の用意
Composite は、複合物を意味します。今回は、フォルダとファイルの混合物を用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package main import ( "fmt" "log" "os" ) // 混合物 type FolderObject struct { Path, Name string Files []FileObject SubFolders []FolderObject } type FileObject struct { Name string } |
FolderObjectには、自分自身とFileObjectのスライスを格納しています。これが、Compositeパターンのポイントです。
プロパティとして自分自身を含めると、再帰処理の呼び出しを容易にします。
コンストラクタの定義
次に、コンストラクタを定義しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// コンストラクタ func NewFolder(path, name string) *FolderObject { return &FolderObject{ Path: path, Name: name, Files: nil, SubFolders: nil, } } func NewFile(name string) *FileObject { return &FileObject{Name: name} } |
FileObjectのメソッド
FileObjectのメソッドを実装しましょう。
1 2 3 4 5 6 7 8 |
func (f *FileObject) createFile(path, folderName, fileName string) { pathAndName := fmt.Sprintf("%s%s/%s", path, folderName, fileName) _, err := os.Create(pathAndName) if err != nil { log.Fatal(fmt.Sprintf("%sファイルの作成に失敗しました。", fileName)) } fmt.Println(fmt.Sprintf("%sファイルを作成しました。", fileName)) } |
FolderObjectのメソッド
FolderObjectのメソッドを実装しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
func (f *FolderObject) Create() { f.createFolder(f.Path, f.Name) } func (f *FolderObject) createFolder(path, name string) { pathAndName := fmt.Sprintf("%s%s", path, name) err := os.Mkdir(pathAndName, 0777) if err != nil { fmt.Println(err) log.Fatal(fmt.Sprintf("%sディレクトリの作成に失敗しました。", name)) } fmt.Println(fmt.Sprintf("%sディレクトリを作成しました。", name)) // 再帰的な呼び出し for _, file := range f.Files { file.createFile(f.Path, f.Name, file.Name) } // 再帰的な呼び出し for _, folder := range f.SubFolders { folder.createFolder(folder.Path, folder.Name) } } |
createFolderが、再帰対象のメソッドです。
ここでは、FolderObjectのスライスであるFilesの中身をループで回し、処理を再帰的に呼び出しています。
1 2 3 4 |
for _, folder := range f.SubFolders { // 自分自身を呼び出す folder.createFolder(folder.Path, folder.Name) } |
面白いですよね。
FolderObjectに自分自身を含めているから、こんなことができるんですよね。
使ってみよう
このプログラムは、次の様に呼び出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
func main() { // ルートフォルダを作成 folder := NewFolder("./", "Sample1") // ファイルを追加 folder.Files = append( folder.Files, *NewFile("a.go"), *NewFile("b.go")) // サブフォルダを作成 folder2 := NewFolder("./Sample1/", "Sample2") // ファイルを追加 folder2.Files = append( folder2.Files, *NewFile("c.go"), *NewFile("d.go")) // サブフォルダを作成 folder3 := NewFolder("./Sample1/", "Sample3") // ファイルを追加 folder3.Files = append( folder3.Files, *NewFile("e.go"), *NewFile("f.go")) // サブフォルダを追加 folder.SubFolders = append( folder.SubFolders, *folder2, *folder3) folder.Create() } |
プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 |
$ go run main.go Sample1ディレクトリを作成しました。 a.goファイルを作成しました。 b.goファイルを作成しました。 Sample2ディレクトリを作成しました。 c.goファイルを作成しました。 d.goファイルを作成しました。 Sample3ディレクトリを作成しました。 e.goファイルを作成しました。 f.goファイルを作成しました。 |
OKですね。ちゃんとディレクトリ → ファイルの順で、再帰的に処理を呼び出していることがわかります。
Compositeパターン:Interface有り
今度は、Interface有りバージョンで実装してみます。
Compositeインターフェース
名前はなんでも良いのですが、インターフェースを用意します。
1 2 3 4 |
// インターフェース type Component interface { create(string, string) } |
ここに定義したcreateは、プログラム実行時に再帰的に呼び出されます。
容器の用意
容器を用意します。
1 2 3 4 5 6 7 8 9 10 |
// 混合物 type FolderObject struct { Path, Name string Files []FileObject SubFolders []FolderObject } type FileObject struct { Name string } |
コンストラクタの定義
コンストラクタを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// コンストラクタ func NewFolder(path, name string) *FolderObject { return &FolderObject{ Path: path, Name: name, Files: nil, SubFolders: nil, } } func NewFile(name string) *FileObject { return &FileObject{Name: name} } |
FileObjectのメソッド
インターフェースの要件を満たすように、createメソッドを実装します。
1 2 3 4 5 6 7 8 |
func (f *FileObject) create(path, name string) { pathAndName := fmt.Sprintf("%s%s", path, name) _, err := os.Create(pathAndName) if err != nil { log.Fatal(fmt.Sprintf("%sファイルの作成に失敗しました。", name)) } fmt.Println(fmt.Sprintf("%sファイルを作成しました。", name)) } |
FolderObjectのメソッド
インターフェースの要件を満たすように、createメソッドを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func (f *FolderObject) create(path, name string) { pathAndName := fmt.Sprintf("%s%s", path, name) err := os.Mkdir(pathAndName, 0777) if err != nil { fmt.Println(err) log.Fatal(fmt.Sprintf("%sディレクトリの作成に失敗しました。", name)) } fmt.Println(fmt.Sprintf("%sディレクトリを作成しました。", name)) // 再帰的な呼び出し for _, file := range f.Files { file.create(f.Path, f.Name+"/"+file.Name) } // 再帰的な呼び出し for _, folder := range f.SubFolders { folder.create(folder.Path, folder.Name) } } |
また、FolderObjectに、データ追加用のメソッドを実装します。
1 2 3 4 5 6 7 8 9 |
// インターフェースをパラメータとして持つ func (f *FolderObject) Add(c Component) { switch t := c.(type) { case *FolderObject: f.SubFolders = append(f.SubFolders, *t) case *FileObject: f.Files = append(f.Files, *t) } } |
このメソッドの引数には、Componentインターフェースを指定しています。そのため、パラメータとして渡すインスタンスはcreteメソッドを持つことが保証されます。
使ってみよう
このプログラムは、以下のように利用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
func main() { // ルートフォルダを作成 rootFolder := NewFolder("./", "Sample1") // ファイルを追 rootFolder.Add(NewFile("a.go")) rootFolder.Add(NewFile("b.go")) // サブフォルダを作成 folder2 := NewFolder("./Sample1/", "Sample2") // ファイルを追加 folder2.Add(NewFile("c.go")) folder2.Add(NewFile("d.go")) // サブフォルダを作成 folder3 := NewFolder("./Sample1/", "Sample3") // ファイルを追加 folder3.Add(NewFile("e.go")) folder3.Add(NewFile("f.go")) // サブフォルダを追加 rootFolder.Add(folder2) rootFolder.Add(folder3) rootFolder.create(rootFolder.Path, rootFolder.Name) } |
インターフェースを実装していないバージョンと比べると、結構いい感じになりましたね。プログラムを実行してみましょう。
1 2 3 4 5 6 7 8 9 10 |
$ go run main.go Sample1ディレクトリを作成しました。 Sample1/a.goファイルを作成しました。 Sample1/b.goファイルを作成しました。 Sample2ディレクトリを作成しました。 Sample2/c.goファイルを作成しました。 Sample2/d.goファイルを作成しました。 Sample3ディレクトリを作成しました。 Sample3/e.goファイルを作成しました。 Sample3/f.goファイルを作成しました。 |
OKですね。
まとめ
Go言語でCompositeパターンを紹介している記事があまりないので、この実装で合っているか、ぶっちゃけあまり自信ありませんw
しかし、きっと大丈夫でしょう!
Compositeパターンは、再帰処理を実装するときに活躍するので、今回紹介したプログラム以外にも、データサーチなんかにも応用ができそうです。
構造体の中に子要素があって、されにその子要素が…と続いていく処理を実装したい時、このパターンを思い出していただければと思います^^
それでは、また!
Go言語まとめ
ソースコード
Interface無し
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
package main import ( "fmt" "log" "os" ) // 混合物 type FolderObject struct { Path, Name string Files []FileObject SubFolders []FolderObject } // コンストラクタ func NewFolder(path, name string) *FolderObject { return &FolderObject{ Path: path, Name: name, Files: nil, SubFolders: nil, } } func (f *FolderObject) Create() { f.createFolder(f.Path, f.Name) } func (f *FolderObject) createFolder(path, name string) { pathAndName := fmt.Sprintf("%s%s", path, name) err := os.Mkdir(pathAndName, 0777) if err != nil { fmt.Println(err) log.Fatal(fmt.Sprintf("%sディレクトリの作成に失敗しました。", name)) } fmt.Println(fmt.Sprintf("%sディレクトリを作成しました。", name)) // 再帰的な呼び出し for _, file := range f.Files { file.createFile(f.Path, f.Name, file.Name) } // 再帰的な呼び出し for _, folder := range f.SubFolders { folder.createFolder(folder.Path, folder.Name) } } type FileObject struct { Name string } // コンストラクタ func NewFile(name string) *FileObject { return &FileObject{Name: name} } func (f *FileObject) createFile(path, folderName, fileName string) { pathAndName := fmt.Sprintf("%s%s/%s", path, folderName, fileName) _, err := os.Create(pathAndName) if err != nil { log.Fatal(fmt.Sprintf("%sファイルの作成に失敗しました。", fileName)) } fmt.Println(fmt.Sprintf("%sファイルを作成しました。", fileName)) } func main() { // ルートフォルダを作成 folder := NewFolder("./", "Sample1") // ファイルを追加 folder.Files = append( folder.Files, *NewFile("a.go"), *NewFile("b.go")) // サブフォルダを作成 folder2 := NewFolder("./Sample1/", "Sample2") // ファイルを追加 folder2.Files = append( folder2.Files, *NewFile("c.go"), *NewFile("d.go")) // サブフォルダを作成 folder3 := NewFolder("./Sample1/", "Sample3") // ファイルを追加 folder3.Files = append( folder3.Files, *NewFile("e.go"), *NewFile("f.go")) // サブフォルダを追加 folder.SubFolders = append( folder.SubFolders, *folder2, *folder3) folder.Create() } |
Interface有り
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
package main import ( "fmt" "log" "os" ) // インターフェース type Component interface { create(string, string) } // 混合物 type FolderObject struct { Path, Name string Files []FileObject SubFolders []FolderObject } // コンストラクタ func NewFolder(path, name string) *FolderObject { return &FolderObject{ Path: path, Name: name, Files: nil, SubFolders: nil, } } // インターフェースをパラメータとして持つ func (f *FolderObject) Add(c Component) { switch t := c.(type) { case *FolderObject: f.SubFolders = append(f.SubFolders, *t) case *FileObject: f.Files = append(f.Files, *t) } } func (f *FolderObject) create(path, name string) { pathAndName := fmt.Sprintf("%s%s", path, name) err := os.Mkdir(pathAndName, 0777) if err != nil { fmt.Println(err) log.Fatal(fmt.Sprintf("%sディレクトリの作成に失敗しました。", name)) } fmt.Println(fmt.Sprintf("%sディレクトリを作成しました。", name)) // 再帰的な呼び出し for _, file := range f.Files { file.create(f.Path, f.Name+"/"+file.Name) } // 再帰的な呼び出し for _, folder := range f.SubFolders { folder.create(folder.Path, folder.Name) } } type FileObject struct { Name string } // コンストラクタ func NewFile(name string) *FileObject { return &FileObject{Name: name} } func (f *FileObject) create(path, name string) { pathAndName := fmt.Sprintf("%s%s", path, name) _, err := os.Create(pathAndName) if err != nil { log.Fatal(fmt.Sprintf("%sファイルの作成に失敗しました。", name)) } fmt.Println(fmt.Sprintf("%sファイルを作成しました。", name)) } func main() { // ルートフォルダを作成 rootFolder := NewFolder("./", "Sample1") // ファイルを追 rootFolder.Add(NewFile("a.go")) rootFolder.Add(NewFile("b.go")) // サブフォルダを作成 folder2 := NewFolder("./Sample1/", "Sample2") // ファイルを追加 folder2.Add(NewFile("c.go")) folder2.Add(NewFile("d.go")) // サブフォルダを作成 folder3 := NewFolder("./Sample1/", "Sample3") // ファイルを追加 folder3.Add(NewFile("e.go")) folder3.Add(NewFile("f.go")) // サブフォルダを追加 rootFolder.Add(folder2) rootFolder.Add(folder3) rootFolder.create(rootFolder.Path, rootFolder.Name) } |
コメントを残す
コメントを投稿するにはログインしてください。