Saat membuat object diperlukan proses construction, ada yang simple, ada yang membutuhkan banyak argument.
Tujuan dari design pattern builder ini adalah jika proses construction object kompleks, dapat dilakukan secara bertahap.
Kita akan bahas pattern builder menggunakan builder built-in dari Go yaitu String builder. String builder berguna untuk menggabungkan string.
Skenarionya adalah Anda akan membuat web server yang menampilkan html.
package main import ( "fmt" "strings" ) func main() { hello := "hello" sb := strings.Builder{} sb.WriteString("<p>") sb.WriteString(hello) sb.WriteString("</p>") fmt.Printf(sb.String()) }
Dapat dilihat dari kode diatas, proses membuat string html yang akan diserve akan menjadi rumit. Contoh kode diatas baru membahas element paragraph, belum lagi element yang lainnya.
Pattern Builder
Kita dapat gunakan pattern builder untuk membuat object htmlElement builder, dimana user bisa secara bertahap dalam membuat html.
Untuk penjelasan code pada baris comment.
package main import ( "fmt" "strings" ) const ( indentSize = 2 ) type HtmlElement struct { name, text string elements []HtmlElement } func (e *HtmlElement) String() string { return e.string(0) } //fungsi untuk mengenerate html text (html element (open dan close tag), identasi) func (e *HtmlElement) string(indent int) string { sb := strings.Builder{} i := strings.Repeat(" ", indentSize*indent) sb.WriteString(fmt.Sprintf("%s<%s>\n", i, e.name)) if len(e.text) > 0 { sb.WriteString(strings.Repeat(" ", indentSize*(indent+1))) sb.WriteString(e.text) sb.WriteString("\n") } for _, el := range e.elements { sb.WriteString(el.string(indent + 1)) } sb.WriteString(fmt.Sprintf("%s</%s>\n", i, e.name)) return sb.String() } //pattern builder untuk membuat object html type HtmlBuilder struct { rootName string root HtmlElement } //constructor cukup menerima root element func NewHtmlBuilder(rootName string) *HtmlBuilder { return &HtmlBuilder{rootName, HtmlElement{rootName, "", []HtmlElement{}}} } //fungsi ini akan memanggil fungsi string pada interface HtmlElement func (builder *HtmlBuilder) String() string { return builder.root.String() } //fungsi utilitas untuk menambahkan element kedalam root element secara bertahap func (builder *HtmlBuilder) AddChild( childName string, childText string) { e := HtmlElement{childName, childText, []HtmlElement{}} builder.root.elements = append( builder.root.elements, e) } func main() { // hello := "hello" // sb := strings.Builder{} // sb.WriteString("<p>") // sb.WriteString(hello) // sb.WriteString("</p>") // fmt.Printf("%s\n", sb.String()) b := NewHtmlBuilder("ul") b.AddChild("li", "hello") b.AddChild("li", "world") fmt.Println(b.String()) }
Fluent Interface
Pada builder pattern juga terdapat fluent interface, yang memungkinkan chaining call dengan cara melakukan return argument dari function. Dalam contoh dibawah, return argument adalah *HtmlBuilder.
Berikut contoh cara membuat fluent interface pada method AddChild. (bandingkan fungsi AddChild dengan AddChildFluent).
func (builder *HtmlBuilder) AddChild(
childName string, childText string) {
e := HtmlElement{childName, childText, []HtmlElement{}}
builder.root.elements = append(
builder.root.elements, e)
}
func (builder *HtmlBuilder) AddChildFluent(
childName string, childText string) *HtmlBuilder {
e := HtmlElement{childName, childText, []HtmlElement{}}
builder.root.elements = append(
builder.root.elements, e)
return builder
}
Berikut hasil akhirt code menggunakan chaining call.
package main import ( "fmt" "strings" ) const ( indentSize = 2 ) type HtmlElement struct { name, text 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.name)) if len(e.text) > 0 { sb.WriteString(strings.Repeat(" ", indentSize*(indent+1))) sb.WriteString(e.text) sb.WriteString("\n") } for _, el := range e.elements { sb.WriteString(el.string(indent + 1)) } sb.WriteString(fmt.Sprintf("%s</%s>\n", i, e.name)) return sb.String() } type HtmlBuilder struct { rootName string root HtmlElement } func NewHtmlBuilder(rootName string) *HtmlBuilder { return &HtmlBuilder{rootName, HtmlElement{rootName, "", []HtmlElement{}}} } func (builder *HtmlBuilder) String() string { return builder.root.String() } func (builder *HtmlBuilder) AddChild( childName string, childText string) { e := HtmlElement{childName, childText, []HtmlElement{}} builder.root.elements = append( builder.root.elements, e) } func (builder *HtmlBuilder) AddChildFluent( childName string, childText string) *HtmlBuilder { e := HtmlElement{childName, childText, []HtmlElement{}} builder.root.elements = append( builder.root.elements, e) return builder } func main() { b := NewHtmlBuilder("ul") b.AddChildFluent("li", "hello"). AddChildFluent("li", "world") fmt.Println(b.String()) }
Dari contoh diatas, pendekatan builder pattern, memungkin kita untuk mengkonstruksi object html bertahap dengan menggunakan fungsi utilitas (fungsi AddChild).
Selain itu, code-code repetitif dapat disimpan sebagai function utility, dimana pengguna akhir tidak perlu mengetahuinya.