Creational Design Pattern: Builder – Part 1

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.

Sharing is caring:

Leave a Comment