GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Builder」を学ぶ"
今回は、Pythonで実装した” Builder”のサンプルアプリをGolangで実装し直してみました。
■ Builder(ビルダー)
Builderパターン(ビルダー・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする。
UML class and sequence diagram
UML class diagram
■ "Builder"のサンプルプログラム
Builderパターンとは、複雑な構造のものを一気に組み立てるのは難しいので、事前に全体を構成する各部分をつくって、段階を踏んで構造を持ったインスタンスを組み上げていくものらしいです。
実際に、Builderパターンを使って、「文書」を作成するサンプルプログラムを動かしてみて、動作の様子を確認したいと思います。
-
plain
モードで、動作させると、プレーンテキスト形式の文書が出力されます。 -
html
モードで、動作させると、テーブル形式のリンク集のHTMLファイルが生成されます。
(1) plain
モードで動かしてみる
まずは、プレーンテキスト形式の文書が出力するコードを動かしてみます。
$ go run Main.go plain
======================
# Greeting
*** From the morning to the afternoon ***
- Good morning
- Hello
*** In the evening ***
- Good evening
- Good night
- Good bye
======================
所謂、プレーンテキストの文書が出力されました。
(2) html
モードを動かしてみる
つづいて、TableベースのWebページを作成するコードを動かしてみます。
$ go run Main.go html
[Greeting.html] was created.
Greeting.html
というファイルが生成されました。
Webブラウザで、見た目を確認してみると、こんな感じでした。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Builder
- ディレクトリ構成
.
├── Greeting.html
├── Main.go
└── builder
├── builder.go
├── director.go
├── html_builder.go
└── text_builder.go
(1) Builder(建築者)の役
Builder
役は、インスタンスを作成するためのインタフェースを定めます。
Builder
役には、インスタンスの各部分を作るためのメソッドが用意されます。
サンプルプログラムでは、builder
インタフェースが、この役を努めます。
package builder
type builder interface {
makeTitle(title string)
makeString(str string)
makeItems(items []string)
close()
}
(2) ConcreteBuilder(具体的建築者)の役
ConcreteBuilder
役は、Builder
役のインタフェースを実装しているクラスです。実際のインスタンス作成で呼び出されるメソッドが、ここで定義されます。また、最終的にできた結果を得るためのメソッドが用意されます。
サンプルプログラムでは、TextBuilder
構造体や、HTMLBuilder
構造体が、この役を努めます。
package builder
import "fmt"
// TextBuilder is struct
type TextBuilder struct {
buffer string
}
// NewTextBuilder func for initializing TextBuilder
func NewTextBuilder() *TextBuilder {
return &TextBuilder{}
}
func (t *TextBuilder) makeTitle(title string) {
t.buffer += "======================\n"
t.buffer += fmt.Sprintf("# %s\n", title)
t.buffer += "\n"
}
func (t *TextBuilder) makeString(str string) {
t.buffer += fmt.Sprintf("*** %s ***\n", str)
}
func (t *TextBuilder) makeItems(items []string) {
for _, i := range items {
t.buffer += fmt.Sprintf("- %s\n", i)
}
}
func (t *TextBuilder) close() {
t.buffer += "======================\n"
}
// GetResult func for fetching all from buffer
func (t *TextBuilder) GetResult() string {
return t.buffer
}
package builder
import (
"fmt"
"os"
)
// HTMLBuilder is struct
type HTMLBuilder struct {
buffer, filename string
f *os.File
makeTitleCalled bool
}
// NewHTMLBuilder func for initializing HTMLBuilder
func NewHTMLBuilder() *HTMLBuilder {
return &HTMLBuilder{}
}
func (h *HTMLBuilder) makeTitle(title string) {
h.filename = fmt.Sprintf("%s.html", title)
h.f, _ = os.Create(h.filename)
h.f.Write([]byte(fmt.Sprintf("<html><head><title>%s</title></head></html>", title)))
h.f.Write([]byte(fmt.Sprintf("<h1>%s</h1>", title)))
h.makeTitleCalled = true
}
func (h *HTMLBuilder) makeString(str string) {
if !h.makeTitleCalled {
os.Exit(1)
}
h.f.Write([]byte(fmt.Sprintf("<p>%s</p>", str)))
}
func (h *HTMLBuilder) makeItems(items []string) {
if !h.makeTitleCalled {
os.Exit(1)
}
h.f.Write([]byte("<ul>"))
for _, i := range items {
h.f.Write([]byte(fmt.Sprintf("<li>%s</li>", i)))
}
h.f.Write([]byte("</ul>"))
}
func (h *HTMLBuilder) close() {
if !h.makeTitleCalled {
os.Exit(1)
}
h.f.Write([]byte("</body></html>"))
h.f.Close()
}
// GetResult func for replying filename
func (h *HTMLBuilder) GetResult() string {
return h.filename
}
(3) Director(監督者)の役
Director
役は、Builder
役のインタフェースを使って、インスタンスを生成します。
ConcreteBuilder
役に依存したプログラミングは行いません。ConcreteBuilder
役が何であってもうまく動作するように、Builder
役のメソッドのみを使います。
サンプルプログラムでは、Director
タイプが、この役を努めます。
package builder
// Director is struct
type Director struct {
builder builder
}
// NewDirector func for initializing Director
func NewDirector(builder builder) *Director {
return &Director{
builder: builder,
}
}
// Construct func for conducting some methods of builder
func (d *Director) Construct() {
d.builder.makeTitle("Greeting")
d.builder.makeString("From the morning to the afternoon")
d.builder.makeItems([]string{"Good morning", "Hello"})
d.builder.makeString("In the evening")
d.builder.makeItems([]string{"Good evening", "Good night", "Good bye"})
d.builder.close()
}
(4) Client(依頼人)の役
Builder
役を利用する役です。
サンプルプログラムでは、startMain
関数が、この役を努めます。
package main
import (
"flag"
"fmt"
"./builder"
)
func startMain(opt string) {
if opt == "plain" {
builderObj := builder.NewTextBuilder()
director := builder.NewDirector(builderObj)
director.Construct()
result := builderObj.GetResult()
fmt.Println(result)
} else if opt == "html" {
builderObj := builder.NewHTMLBuilder()
director := builder.NewDirector(builderObj)
director.Construct()
result := builderObj.GetResult()
fmt.Printf("[%s] was created.\n", result)
}
}
func main() {
flag.Parse()
startMain(flag.Arg(0))
}