saeki’s blog

The limits of my code mean the limits of my world.

GoFデザインパターン ~Builderパターン~

こちらを読みながら進める。

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門

Builderパターンとは

複雑なインスタンスなどの生成において、骨組みと部品の生成を分離するパターン。 Builderが各部品を作るインターフェースを持ち、監督者であるDirectorがBuilderのインターフェースを使って部品を組み立てていく。
ここでのポイントは、DirectorはBuilderのインターフェースのみ着目するためBuilderの具体的な実装には依存せず、Builderが交換可能であるということ。

f:id:t_saeki:20190821203613p:plain

クラス図あまり綺麗に書けなかった..

実装

増補改訂版Java言語で学ぶデザインパターン入門で使われてる例をGoで実装してみる。

ディレクトリ構成は以下

./7_builder
├── builder
│   ├── builder.go
│   ├── html_builder.go
│   └── text_builder.go
├── director
    ├── director.go
│   └── document_builder.go
└── main.go

Director

構造の生成過程で監督者となるDirector。Builderインターフェスを使って構造を作っていく。コードが複雑になると複数のDirectorが必要になりそうなので、Director-ConcreteDirectorの関係を作りConcreteDirectorはDirectorインターフェースを実装するようにした。こうすることでClientにおいてDirectorも交換可能となる。

package director

import "github.com/saekis/go-design-pattern/7_builder/builder"

type Director interface {
    Build() string
}

type documentDirector struct {
    builder builder.Builder
}

func NewDocumentDirector(builder builder.Builder) Director {
    return &documentDirector{builder: builder}
}

func (director *documentDirector) Build() string {
    director.builder.MakeTitle("title")
    director.builder.MakeString("string")
    director.builder.MakeItems([]string{
        "item1",
        "item2",
    })
    director.builder.Close()
    return director.builder.GetResult()
}

Builder

Builderで各部品を生成する具体的な実装をおこなう。

Builderインターフェース
package builder

type Builder interface {
    MakeTitle(string)
    MakeString(string)
    MakeItems([]string)
    Close()
    GetResult() string
}
ConcreteBuilder

TextBuilder

package builder

import (
    "bytes"
    "fmt"
)

type textBuilderImple struct {
    buf bytes.Buffer
}

func NewTextBuilder() Builder {
    return &textBuilderImple{buf: bytes.Buffer{}}
}

func (b *textBuilderImple) MakeTitle(title string) {
    b.buf.WriteString(fmt.Sprintf("「%s」\n\n", title))
}

func (b *textBuilderImple) MakeString(str string) {
    b.buf.WriteString(fmt.Sprintf("%s\n", str))
}

func (b *textBuilderImple) MakeItems(items []string) {
    for _, item := range items {
        b.buf.WriteString(fmt.Sprintf("・%s\n", item))
    }
}

func (b *textBuilderImple) Close() {
    // Do nothing
}

func (b *textBuilderImple) GetResult() string {
    return b.buf.String()
}

HtmlBuilder

package builder

import (
    "bytes"
    "fmt"
)

type htmlBuilderImple struct {
    buf bytes.Buffer
}

func NewHtmlBuilder() Builder {
    return &htmlBuilderImple{buf: bytes.Buffer{}}
}

func (b *htmlBuilderImple) MakeTitle(title string) {
    b.buf.WriteString(fmt.Sprintf("<html><head><title>%s</title></head><body>", title))
    b.buf.WriteString(fmt.Sprintf("<h1>%s</h1>", title))
}

func (b *htmlBuilderImple) MakeString(str string) {
    b.buf.WriteString(fmt.Sprintf("<p>%s</p>", str))
}

func (b *htmlBuilderImple) MakeItems(items []string) {
    b.buf.WriteString("<ul>")
    for _, item := range items {
        b.buf.WriteString(fmt.Sprintf("<li>%s</li>", item))
    }
    b.buf.WriteString("</ul>")
}

func (b *htmlBuilderImple) Close() {
    b.buf.WriteString("</body></html>")
}

func (b *htmlBuilderImple) GetResult() string {
    return b.buf.String()
}

Client

ClientがBuilderを初期化してそれをDirectorに食わせる。
ClientではDirectorインターフェースのみ着目するため、DirectorとBuilderの具体的な処理は知らない。

package main

import (
    "fmt"

    "github.com/saekis/go-design-pattern/7_builder/director"

    "github.com/saekis/go-design-pattern/7_builder/builder"
)

func main() {
    // Build text
    b := builder.NewTextBuilder()
    d := director.NewDocumentDirector(b)
    result := d.Build()
    fmt.Println(result)

    // Build html
    b = builder.NewHtmlBuilder()
    d = director.NewDocumentDirector(b)
    result = d.Build()
    fmt.Println(result)
}

実行結果

$ go run main.go 
# text
「title」

string
・item1
・item2

# html
<html><head><title>title</title></head><body><h1>title</h1><p>string</p><ul><li>item1</li><li>item2</li></ul></body></html>

実装したもの

github.com