Golang Design Patterns - Builder. Khởi tạo những đối tượng phức tạp bằng phương pháp step by step
I. Builder - Creational Pattern
- Một cách tổng quan hơn, Builder được sinh ra để:
reusing an algorithm to create many implementations of an interface
. Design Pattern này giúp chúng ta xây dựng các đối tượng phức tạp mà không cần trực tiếp khởi tạo cấu trúc hoặc implement ngay lập tức các logic mà đối tượng yêu cầu. - Hình dung chúng ta có một đối tượng phức tạp với hàng tá fields/methods bên trong, và một vài đối tượng khác cũng sở hữu số lượng fields/methods nhiều như vậy. Sẽ thật khó khăn nếu phải implements toàn bộ constructor cho những đối tượng đó, và ngay tại lúc development, chúng ta hoàn toàn không biết được số lượng loại object có thể được tạo ra. Khi đó Builder Pattern sẽ giúp ích cho chúng ta.
II. Khi nào nên sử dụng Builder
- Mình sẽ cho một trường hợp cụ thể để các bạn dễ hình dung nhé. Để tạo ra xe đạp và xe máy, người ta phải trải qua rất nhiều công đoạn như build khung sườn, build bánh xe, build chỗ ngồi...Ở đây chúng ta hoàn toàn có thể tận dụng lại các step đã define trước đó dành cho xe đạp, mà không cần phải define lại toàn bộ cho xe máy
- Quay về định nghĩa, Builder design pattern cố gắng adapt những điều sau:
- Trừu tượng hoá những khởi tạo đối tượng phức tạp
- Tạo đối tượng một cách
step by step
, nghĩa là ngay lần đầu khởi tạo, đối tượng chắc chắn sẽ chưa hoàn chỉnh, chúng ta có thể tuỳ ý custom bằng cáchbuild
thêm các fields/thuộc tính vào đó - Tái sử dụng các công đoạn khởi tạo từ các đối tượng riêng biệt
III. Ví dụ thực tế
Tiếp tục với ví dụ ở trên, mình sẽ tạo ra 1 đối tượng là Vehicle builder và một đối tượng duy nhất là ManufacturingDirector
với chức năng nhận vào builders (Bicycle or Motorbike) và constructs products. Với những nguyên tắc sau:
- Chúng ta có manufacturing type để define cấu trúc những thứ mà Vehicle cần
- Khi sử dụng bicycle builder, đối tượng
VehicleProduct
bao gồm 2 bánh xe, 1 chỗ ngồi, 1 structure field với type làBicycle
sẽ được trả về. - Khi sử dụng motorbike builder, đối tượng
VehicleProduct
bao gồm 2 bánh xe, 2 chỗ ngồi, 1 structure field với type làMotorbike
được trả về. - Đối tượng
VehicleProduct
được tạo ra từBuild Process
builder luôn phải được open to modifications.
IV. Implementation
- Trước tiên chúng ta define VehicleProduct struct, bao gồm các thuộc tính Wheels, Seats, Structure:
// vehicle_product.go
package builder
type VehicleProduct struct {
Wheels int
Seats int
Structure string
}
- Kế đến, để tạo ra một Vehicle, ta cần define một common process, ở đây mình tạo ra:
// build_process.go
package builder
type BuildProcess interface {
SetWheels() BuildProcess
SetSeats() BuildProcess
SetStructure() BuildProcess
GetVehicle() VehicleProduct
}
- BuildProcess bao gồm các method SetWheels, SetSeats, SetStructure, các method này thể hiện các công việc cần làm để build một Vehicle, lưu ý rằng return type của các method này luôn là BuildProcess. Thêm vào đó có
GetVehicle
, đơn giản chỉ để get thông tin Vehicle mà thôi. - Tiếp theo là BicycleBuilder
// bicycle_builder.go
package builder
type BicycleBuilder struct {
v VehicleProduct
}
func (c *BicycleBuilder) SetWheels() BuildProcess {
c.v.Wheels = 2
return c
}
func (c *BicycleBuilder) SetSeats() BuildProcess {
c.v.Seats = 1
return c
}
func (c *BicycleBuilder) SetStructure() BuildProcess {
c.v.Structure = "Bicycle"
return c
}
func (c *BicycleBuilder) GetVehicle() VehicleProduct {
return c.v
}
- và MotorbikeBuilder
// motorbike_builder.go
package builder
type MotorbikeBuilder struct {
v VehicleProduct
}
func (c *MotorbikeBuilder) SetWheels() BuildProcess {
c.v.Wheels = 2
return c
}
func (c *MotorbikeBuilder) SetSeats() BuildProcess {
c.v.Seats = 2
return c
}
func (c *MotorbikeBuilder) SetStructure() BuildProcess {
c.v.Structure = "Motorbike"
return c
}
func (c *MotorbikeBuilder) GetVehicle() VehicleProduct {
return c.v
}
- Tiếp theo, như ví dụ nói ở phần trước, chúng ta define một đối tượng
ManufacturingDirector
, chịu trách nhiệm nhận vào một Vehicle Builder và construct những common step để cấu thành một Vehicle cơ bản nhất:
// manufacturing_director.go
package builder
type ManufacturingDirector struct {
builder BuildProcess
}
func (f *ManufacturingDirector) SetBuilder(b BuildProcess) {
f.builder = b
}
func (f *ManufacturingDirector) Construct() {
f.builder.SetSeats().SetStructure().SetWheels()
}
- Run it:
// main.go
manufacturingVehicle := builder.ManufacturingDirector{}
bicycleBuilder := &builder.BicycleBuilder{}
manufacturingVehicle.SetBuilder(bicycleBuilder)
manufacturingVehicle.Construct()
bicycle := bicycleBuilder.GetVehicle()
fmt.Printf("Vehicle is %s has %d wheels, %d seats.", bicycle.Structure, bicycle.Wheels, bicycle.Seats)
- Kết quả:
V. Lời kết
- Builder Pattern vẫn giúp chúng ta kiểm soát tốt cho dù có bao nhiêu loại Vehicle được sinh ra sau này. Quá trình construct luôn được trừu tượng hoá với user, user sẽ không biết được logic phức tạp khi khởi tạo một Vehicle.
- Hơn nữa, việc define một structure rõ ràng sẽ giúp các developers tuân thủ đúng các step/process được định nghĩa trước đó ở
BuildProcess
Cảm ơn các bạn đã đọc ^^
Source code: https://github.com/khaaleoo/golang-design-patterns
VI. References
- Go Design Patterns (Mario Castro Contreras)
All Rights Reserved