こんにちは、KOUKIです。
この記事では、デザインパターンの一つであるBuilderパターンについて、紹介しています。
<目次>
デザインパターンまとめ
Builderパターンについて
「Builder(建築者)」と名前がついている通り、Builderパターンはインスタンスの生成を得意とするパターンです。※Go言語にはオブジェクトが存在しないので、インスタンスとしています
私たちがプログラムを書く上で、インスタンスを生成して処理する状況は頻繁にあります。その中で、重複する処理を実装してしまったり、誤ったパラメータでインスタンスを生成したり、数十個に及ぶ引数が必要なインスタンスを生成しなければならなかったりなど、多くの問題に出くわすかと思います(人間だもの)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 例えば、こんなインスタンスがあるとして... type Person struct { name string age int work string man bool hobby string like []string ... } // こんな感じで設定していくとミスが生じるかもしれない func main() { p := Person{ name: "hoge", age: 32, work: "IT Enginner", man: true, hobby: "Developement", like: []string{"hoge", "hoge"}, .... } } |
Builderパターンで、シンプルなインスタンス生成にチャンレンジしましょう。
シチュエーション
最初に、Builderパターンを使わない場合の例を挙げます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package main import ( "fmt" "strings" ) func main() { keyWords := []string{"golang", "python", "javascript"} sb := strings.Builder{} sb.WriteString("<ul>") for _, v := range keyWords { sb.WriteString("<li>") sb.WriteString(v) sb.WriteString("</li>") } sb.WriteString("<ul>") fmt.Println(sb.String()) } |
このコードは、HTMLのリストを生成します。
1 2 |
$ go run main.go <ul><li>golang</li><li>python</li><li>javascript</li><ul> |
うまくいきました!
しかし、このコードにはいくつか問題点があります。
2: タグ(ul/li)を間違えたり、閉じタグを忘れるかもしれない
3: 拡張性に乏しい
他にもあるかもしれません。
Builderパターンの適用
先ほどのサンプルにBuilderパターンを適用してみましょう。
HTML生成処理をロジック化する
最初に、HTML生成処理をロジック化します。
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 |
package main import ( "fmt" "strings" ) const indentSize = 2 type HtmlElement struct { tagName, content string elements []HtmlElement } func (e *HtmlElement) String() string { return e.string(0) } func (e *HtmlElement) string(indent int) string { sb := strings.Builder{} // インデントを作成 i := strings.Repeat(" ", indentSize*indent) // 開始タグの作成 sb.WriteString(fmt.Sprintf("%s<%s>\n", i, e.tagName)) // コンテンツを入れ込む if len(e.content) > 0 { sb.WriteString(strings.Repeat(" ", indentSize*indent+1)) sb.WriteString(e.content) sb.WriteString("\n") } // 子要素を作成する for _, el := range e.elements { sb.WriteString(el.string(indent + 1)) } // 閉じタグの作成 sb.WriteString(fmt.Sprintf("%s</%s>\n", i, e.tagName)) return sb.String() } func main(){...} |
いきなり難しくなったと感じるかもしれません。しかし、これはBuilderパターンとは関係ないので、サラッと目を通すだけでOKです。
HtmlElement構造体が、今回生成したいインスタンスの金型です。このように構造体を定義すると、プログラムの処理によって何が生成されるかパッとわかるので、Builderパターン云々は関係なく、普通は定義します。
HtmlElement構造体をインスタンス化し、Stringメソッドを呼び出すことで、サンプルコードと同じ結果(あるいはよりグレートな結果)を得たいと思います。
Builderコンストラクタを導入
続いて、コンストラクタ関数を実装します。コンストラクタとは、インスタンス生成時に実行されるメソッドで、Builderパターンには必須です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
type HtmlBuilder struct { rootTagName string root HtmlElement } // コンストラクタ関数 func NewHtmlBuilder(rootTagName string) *HtmlBuilder { return &HtmlBuilder{ rootTagName: rootTagName, root: HtmlElement{ rootTagName, "", []HtmlElement{}, }, } } |
注目していただきたいのが、NewHtmlBuilder(rootTagName string)関数の引数です。インスタンスを生成するために必要なパラメータが、たったの一つなんですよね。
これにより、インスタンスの生成が簡単、かつ、利用しやすいコードになっています。※テストもしやすい
それじゃあ、どうやって他のパラメータを追加するんだよ、という感じになると思いますが、それはメソッドを別に用意します。
1 2 3 4 5 6 7 8 9 10 |
// インスタンスを呼び出す func (b *HtmlBuilder) String() string { return b.root.String() } // 要素を追加する func (b *HtmlBuilder) AddChildTag(childTagName, childContent string) { e := HtmlElement{childTagName, childContent, []HtmlElement{}} b.root.elements = append(b.root.elements, e) } |
インスタンス生成後に、AddChildTagメソッドを呼び出すことで、要素をいくらでも追加できるようにしました。
Builderを呼び出す
このソースコードは、以下のようにして呼び出します。
1 2 3 4 5 6 7 |
func main() { b := NewHtmlBuilder("ul") b.AddChildTag("li", "golang") b.AddChildTag("li", "python") b.AddChildTag("li", "javascript") fmt.Println(b.String()) } |
シチュエーションのサンプルコードより、断然わかりやすく、作業ミスも起こりにくいコードになっていると思います。
取得できる結果もインデント付きのよりグレートな感じになっています^^
1 2 3 4 5 6 7 8 9 10 11 12 |
$ go run main.go <ul> <li> golang </li> <li> python </li> <li> javascript </li> </ul> |
メソッドを繋げる
AddChildTagの戻り値をインスタンスにすると、メソッドを繋げることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
func (b *HtmlBuilder) AddChildTagFluent(childTagName, childContent string) *HtmlBuilder { e := HtmlElement{childTagName, childContent, []HtmlElement{}} b.root.elements = append(b.root.elements, e) return b } func main() { b := NewHtmlBuilder("ul") b.AddChildTagFluent("li", "golang"). AddChildTagFluent("li", "python"). AddChildTagFluent("li", "javascript") fmt.Println(b.String()) } |
これもBuilderパターンのメリットですね。デザインパターンは面白いです。
次回
次回は、BuilderパターンのFacetsバージョンを学びます。
Go言語まとめ
ソースコード
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 |
package main import ( "fmt" "strings" ) const indentSize = 2 type HtmlElement struct { tagName, content string elements []HtmlElement } func (e *HtmlElement) String() string { return e.string(0) } func (e *HtmlElement) string(indent int) string { sb := strings.Builder{} // インデントを作成 i := strings.Repeat(" ", indentSize*indent) // 開始タグの作成 sb.WriteString(fmt.Sprintf("%s<%s>\n", i, e.tagName)) // コンテンツを入れ込む if len(e.content) > 0 { sb.WriteString(strings.Repeat(" ", indentSize*indent+1)) sb.WriteString(e.content) sb.WriteString("\n") } // 子要素を作成する for _, el := range e.elements { sb.WriteString(el.string(indent + 1)) } // 閉じタグの作成 sb.WriteString(fmt.Sprintf("%s</%s>\n", i, e.tagName)) return sb.String() } type HtmlBuilder struct { rootTagName string root HtmlElement } func NewHtmlBuilder(rootTagName string) *HtmlBuilder { return &HtmlBuilder{ rootTagName: rootTagName, root: HtmlElement{ rootTagName, "", []HtmlElement{}, }, } } func (b *HtmlBuilder) String() string { return b.root.String() } func (b *HtmlBuilder) AddChildTag(childTagName, childContent string) { e := HtmlElement{childTagName, childContent, []HtmlElement{}} b.root.elements = append(b.root.elements, e) } func (b *HtmlBuilder) AddChildTagFluent(childTagName, childContent string) *HtmlBuilder { e := HtmlElement{childTagName, childContent, []HtmlElement{}} b.root.elements = append(b.root.elements, e) return b } func main() { b := NewHtmlBuilder("ul") b.AddChildTagFluent("li", "golang"). AddChildTagFluent("li", "python"). AddChildTagFluent("li", "javascript") fmt.Println(b.String()) } |
コメントを残す
コメントを投稿するにはログインしてください。