Design Pattern Series - Builder (Golang)
Bài viết trước trong series Singleton Pattern
Khi code Go, chắc hẳn bạn từng gặp các case cần tạo ra struct với rất nhiều tham số, bao gồm cả optional và required. Nếu thêm tất cả vào một constructor thì code sẽ trở nên khó đọc và dễ lỗi.
--> Sau khi tìm hiểu thì mình tìm thấy Builder Pattern, và đây là cách pattern này giúp mình tối ưu lại vấn đề này.
Builder Pattern là gì?
Đây là một pattern thuộc nhóm Creational Design Patterns, ý tưởng chính của nó bao gồm (mình giữ tiếng anh để tránh việc khó hiểu khi translate sang tiếng việt):
- Separate the construction of a complex object from its representation -> thay vì tạo 1 struct với constructor dài, ta chia nhỏ quá trình khởi tạo ra thành từng bước, rồi cuối cùng mới build ra struct hoàn chỉnh (representation)
- Step-by-step construction -> set từng thuộc tính của 1 struct một cách tuần tự
- Avoid telescoping constructors -> Khi struct có quá nhiều tham số optional, việc tạo truyền thống sẽ hơi khó đọc (NewCar("vinfast", "VF9", "green", "$100000", .....)). Với builder, bạn trách được việc phải pass quá nhiều tham số vào một lúc.
Ví dụ không sử dụng Builder:
car := Car{
Brand: "Toyota",
Model: "Camry",
Color: "Black",
Engine: "V6",
Seats: 5,
Sunroof: true,
}
// or use a constructor
car := NewCar("Toyota", "Camry", "Black", "V6", 5, true)
--> Ở đây, việc tạo struct (Car
) và representation (car
) bị gộp chung một chỗ.
- Bạn phải biết hết tất cả tham số.
- Constructor dễ trở nên rối rắm nếu có nhiều tham số tùy chọn
Khi sử dụng builder
car := NewCarBuilder().
SetBrand("Toyota").
SetModel("Camry").
SetColor("Black").
SetEngine("V6").
SetSeats(5).
SetSunroof(true).
Build()
--> Ở đây:
- Quá trình tạo (construction process):
SetBrand
,SetModel
,SetColor
… - Representation:
car
(một instance củaCar
).
Tức là, bạn không trực tiếp tạo Car
ngay, mà thông qua một “người xây dựng” (CarBuilder
) để chuẩn bị từng phần, rồi mới gọi Build()
để xuất ra đối tượng hoàn chỉnh
Như vậy, Builder Pattern đơn giản là:
- Không ép bạn phải tạo struct ngay lập tức.
- Cho phép bạn lắp ráp từng phần, dễ đọc, dễ hiểu.
- Tránh việc constructor quá tải tham số.
--> Khi nào thì dùng Builder Pattern?
- Khi struct có nhiều tham số
- Cần code dễ đọc và bảo trì hơn
- Cần khởi tạo với các cách khác nhau
Triển khai Builder Pattern
Giả sử, ta cần tạo ra 1 struct cho Car
với nhiều thuộc tính khác nhau như: hãng, dòng xe, màu sắc, giá tiền, mã lực, nguyên liệu, số ghế, cốp...
Định nghĩa struct Car
và CarBuilder
package main
import "fmt"
// Product: Car
type Car struct {
Brand string
Model string
Color string
Engine string
Seats int
Sunroof bool
}
// Builder
type CarBuilder struct {
brand string
model string
color string
engine string
seats int
sunroof bool
}
func NewCarBuilder() *CarBuilder {
return &CarBuilder{}
}
func (b *CarBuilder) SetBrand(brand string) *CarBuilder {
b.brand = brand
return b
}
func (b *CarBuilder) SetModel(model string) *CarBuilder {
b.model = model
return b
}
func (b *CarBuilder) SetColor(color string) *CarBuilder {
b.color = color
return b
}
func (b *CarBuilder) SetEngine(engine string) *CarBuilder {
b.engine = engine
return b
}
func (b *CarBuilder) SetSeats(seats int) *CarBuilder {
b.seats = seats
return b
}
func (b *CarBuilder) SetSunroof(sunroof bool) *CarBuilder {
b.sunroof = sunroof
return b
}
func (b *CarBuilder) Build() Car {
return Car{
Brand: b.brand,
Model: b.model,
Color: b.color,
Engine: b.engine,
Seats: b.seats,
Sunroof: b.sunroof,
}
}
Và sử dụng Builder
func main() {
car := NewCarBuilder().
SetBrand("Toyota").
SetModel("Camry").
SetColor("Black").
SetEngine("V6").
SetSeats(5).
SetSunroof(true).
Build()
fmt.Printf("%+v\n", car)
}
Kết quả trả ra
{Brand:Toyota Model:Camry Color:Black Engine:V6 Seats:5 Sunroof:true}
Tuy nhiên thì Builder Pattern cũng có 1 số hạn chế như bạn thấy:
- Code dài hơn để định nghĩa builder
- Struct ít tham số có thể gây overkill (phức tạp hoá vấn đề)
Kết luận
Builder Pattern trong Golang là một lựa chọn tốt khi bạn cần tạo ra những đối tượng phức tạp với nhiều tham số tùy chọn. Nó giúp code trở nên rõ ràng, dễ đọc và dễ mở rộng hơn
Thanks for reading!