LoginSignup
0
2

More than 3 years have passed since last update.

Golangで、デザインパターン「Composite」を学ぶ

Last updated at Posted at 2020-03-25

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

W3sDesign_Composite_Design_Pattern_UML.jpg

UML class diagram

composite.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

書籍「増補改訂版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構造体が、この役を努めます。

composite/file.go
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構造体が、この役を努めます。

composite/directory.go
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インタフェースが、この役を努めます。

composite/entry.go
package composite

type entry interface {
    PrintList(prefix string)
    getSize() int
}

(4) Client(依頼人)の役

サンプルプログラムでは、startMain関数が、この役を努めます。

Main.go
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) その他

エラー時の振る舞いを追加します

composite/file.go
func doError() error {
    msg := "FileTreatmentException"
    return fmt.Errorf("%s", msg)
}

■ 参考URL

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2