Design Pattern Series - Builder (Golang)

Go Design Pattern Series Aug 28, 2025

Previous post in series Singleton Pattern

Khi lập trình với Go, chắc hẳn bạn đã từng gặp trường hợp cần tạo một struct với rất nhiều tham số, bao gồm cả tham số bắt buộc và tùy chọn. Nếu đưa tất cả chúng vào một constructor (hàm khởi tạo), mã nguồn sẽ trở nên cực kỳ khó đọc và dễ dẫn đến sai sót.

Builder Pattern chính là "cứu cánh" giúp tối ưu hóa vấn đề này.

Builder Pattern là gì?

Đây là một mẫu thiết kế thuộc nhóm Creational Design Patterns. Ý tưởng chính của nó bao gồm:

  • Separate the construction of a complex object from its representation: Thay vì tạo một struct với một constructor dài dằng dặc, chúng ta chia quá trình khởi tạo thành từng bước, sau đó mới xây dựng struct hoàn chỉnh (representation).
  • Step-by-step construction: Thiết lập từng thuộc tính của struct một cách tuần tự.
  • Avoid telescoping constructors: Khi một struct có quá nhiều tham số tùy chọn, cách khởi tạo truyền thống sẽ rất khó đọc (ví dụ: NewCar("VinFast", "VF9", "Green", "100.000$", ...)). Với Builder, bạn tránh được việc phải truyền quá nhiều tham số cùng một lúc.

Ví dụ khi không dùng Builder:

car := Car{
    Brand:   "Toyota",
    Model:   "Camry",
    Color:   "Black",
    Engine:  "V6",
    Seats:   5,
    Sunroof: true,
}

// Hoặc sử dụng constructor
car := NewCar("Toyota", "Camry", "Black", "V6", 5, true)

Vấn đề: Ở đây, việc khởi tạo struct (Car) và biểu diễn (car) bị gộp chung một chỗ. Bạn buộc phải biết tất cả tham số, và constructor sẽ dễ dàng gây nhầm lẫn nếu có quá nhiều tham số tùy chọn.

Ví dụ khi dùng Builder:

car := NewCarBuilder().
    SetBrand("Toyota").
    SetModel("Camry").
    SetColor("Black").
    SetEngine("V6").
    SetSeats(5).
    SetSunroof(true).
    Build()

Cơ chế:

  • Construction process: SetBrand, SetModel, SetColor
  • Representation: car (một thực thể của Car).

Nói cách khác, bạn không trực tiếp tạo ra Car, mà sử dụng một “người xây dựng” (CarBuilder) để chuẩn bị từng bộ phận, sau đó gọi Build() để xuất ra đối tượng hoàn chỉnh.

Khi nào nên sử dụng Builder Pattern?

  • Khi một struct có quá nhiều tham số.
  • Khi cần mã nguồn dễ đọc và dễ bảo trì hơn.
  • Khi cần khởi tạo đối tượng theo nhiều cách khác nhau tùy vào điều kiện.

Triển khai Builder Pattern trong Go

Giả sử chúng ta cần tạo một struct cho Car với nhiều thuộc tính khác nhau như thương hiệu, mẫu xe, màu sắc, động cơ, số ghế...

1. Đị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,
	}
}

2. Sử dụng

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ả (Output): {Brand:Toyota Model:Camry Color:Black Engine:V6 Seats:5 Sunroof:true}

Một số hạn chế

Mặc dù rất hữu ích, Builder Pattern vẫn có vài điểm cần lưu ý:

  • Mã nguồn dài hơn vì phải định nghĩa thêm builder struct và các phương thức đi kèm.
  • Nếu struct chỉ có ít tham số, việc áp dụng pattern này có thể gây lãng phí (overkill).

Tổng kết

Builder Pattern trong Golang là lựa chọn tuyệt vời khi bạn cần tạo các đối tượng phức tạp với nhiều tham số tùy chọn. Nó giúp code của bạn rõ ràng, dễ đọc và dễ mở rộng hơn rất nhiều.

Cảm ơn bạn đã đọc bài viết!

Tags