+14

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ách build 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ả: image.png

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí