LoginSignup
1
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-22

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Builder」を学ぶ"

今回は、Pythonで実装した” Builder”のサンプルアプリをGolangで実装し直してみました。

■ Builder(ビルダー)

Builderパターン(ビルダー・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする。

UML class and sequence diagram

W3sDesign_Builder_Design_Pattern_UML.jpg

UML class diagram

2880px-Builder_UML_class_diagram.svg-2.png
(以上、ウィキペディア(Wikipedia)より引用)

■ "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ブラウザで、見た目を確認してみると、こんな感じでした。
tablehtml.png

■ サンプルプログラムの詳細

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

builder/builder.go
package builder

type builder interface {
    makeTitle(title string)
    makeString(str string)
    makeItems(items []string)
    close()
}

(2) ConcreteBuilder(具体的建築者)の役

ConcreteBuilder役は、Builder役のインタフェースを実装しているクラスです。実際のインスタンス作成で呼び出されるメソッドが、ここで定義されます。また、最終的にできた結果を得るためのメソッドが用意されます。
サンプルプログラムでは、TextBuilder構造体や、HTMLBuilder構造体が、この役を努めます。

builder/text_builder.go
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
}
builder/html_builder.go
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タイプが、この役を努めます。

builder/director.go
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関数が、この役を努めます。

Main.go
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))
}

■ 参考URL

1
3
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
1
3