Go – Factory Pattern

Factory pattern là pattern thường được sử dụng trong ngôn ngữ lập trình hướng đối tượng. Trong Go có rất nhiều cách khác nhau trong đó bạn có thể dùng Factory Pattern để làm cho code của bạn trở nên sạch hơn và ngắn gọn hơn.

Go không có class nhưng chúng ta có thể sử dụng struct. Ví dụ struct của Person, cùng với phương thức Greet sẽ như sau

type Person struct {
  Name string
  Age int
}

func (p Person) Greet() {
  fmt.Printf("Hi! My name is %s", p.Name)
}

Factory đơn giản

Cách đơn giản và phổ biến nhất thường được sử dụng là một hàm nhận các số tham số đầu vào và trả về instance Person

func NewPerson(name string, age int) Person {
  return Person{
    Name: name,
    Age: age
  }
}

Chúng ta cũng có thể trả về con trỏ Person như sau

func NewPerson(name string, age int) *Person {
  return &Person{
    Name: name,
    Age: age
  }
}

Các hàm Factory thay thế tốt hơn cho việc khởi tạo các instance  sử dụng như p := &Person{}, bởi vì ký hiệu của hàm đảm bảo rằng tất cả mọi người sẽ cung cấp các thuộc tính yêu cầu. Ví dụ, một người có thể dễ dàng quên việc khởi tạo thuộc tính Age khi sử dụng struct để khởi tạo. Ký hiệu hàm là NewPerson(name string, age int) đảm bảo rằng cả nameage được cung cấp để có thể tạo một Person mới.

Interface factory

Hàm Factory có thể trả về một interface thay vì một struct. Interface cho phép định nghĩa các hành vi (behavior) mà không cần đưa ra các thực thi bên trong. Điều này nghĩa là chúng ta có thể tạo một private struct, trong khi chỉ để lộ interface (ở đây là các hành vi) ra bên ngoài.

type Person interface {
  Greet()
}

type person struct {
  name string
  age int
}

func (p person) Greet() {
  fmt.Printf("Hi! My name is %s", p.name)
}

// Ở đây, NewPerson trả về một interface, và không phải là chính person struct
func NewPerson(name string, age int) Person {
  return person{
    name: name,
    age: age
  }
}

Do con trỏ cũng có thể thực thi các interface, chúng ta có thể trả về hàm giống nhau với con trỏ thay thế

func NewPerson(name string, age int) Person {
  return &person{
    name: name,
    age: age
  }
}

Nhiều phần thực thi

Bằng việc trả về các interface, chúng ta có thể có nhiều hàm Factory trả về các thực thi khác nhau của các interface. Một trường hợp sử dụng thực tế dùng để sinh ra các mock. Ví dụ, chúng ta có thể tạo một interface Doer, http client thực thi và tạo một hàm Factory để trả về mock http client.

// Chúng ta định nghĩa Doer interface, có phương thức 
//  `http.Client` struct `Do` 
type Doer interface {
	Do(req *http.Request) (*http.Response, error)
}

// sử dụng HTTP client thông thường từ package `net/http`
func NewHTTPClient() Doer {
	return &http.Client{}
}

type mockHTTPClient struct{}

func (*mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
	// Phương thức `NewRecorder` của package httptest giúp chúng ta 
	// sinh ra mock request mới
	res := httptest.NewRecorder()

	// Gọi phương thức `Result` cho phép chúng ta
	// tạo object rỗng mặc định *http.Response
	return res.Result(), nil
}

// cho phép tạo mock HTTP client, trả về
// response rỗng cho bất kỳ request nào gửi đến nó
func NewMockHTTPClient() Doer {
	return &mockHTTPClient{}
}

Do NewHTTPClientNewMockHTTPClient cả hai cùng trả về một kiểu, chúng có thể được sử dụng hoán đổi cho nhau, điều này thực sự hữu ích bạn muốn test code sử dụng Http client mà không cần tạo bất kỳ cuộc gọi HTTP ra bên ngoài.

 Factory Generator

Facetory Generator tức là Factory của Factory. Mặc dù nghe có vẻ hơi phức tạp, nhưng Factory Generator là vô cùng hữu ích, khi bạn muốn khởi tạo các instance của các struct khác nhau hoặc các interface không loại trừ lẫn nhau, hoặc bạn muốn nhiều Factory với các giá trị mặc định khác nhau

Phương thức Factory

Chúng ta có thể sử dụng các phương thức của struct Factory Generator. Ví dụ, xem xét một animal factory mà tạo các instance của một animal và cũng là nơi nuôi động vật.

type Animal struct {
	species string
	age     int
}

type AnimalHouse struct {
	name         string
	sizeInMeters int
}

type AnimalFactory struct {
	species   string
	houseName string
}

// NewAnimal là một factory `Animal` cố định 
// species như một giá trị của `AnimalFactory` instance
func (af AnimalFactory) NewAnimal(age int) {
	return Animal{
		species: af.species,
		age:     age,
	}
}

// NewHouse là một factory `AnimalHouse` cố định
// name như một giá trị của `AnimalFactory` instance
func (af AnimalFactory) NewHouse(sizeInMeters int) {
	return Animal{
		name:         af.houseName,
		sizeInMeters: sizeInMeters,
	}
}

Tiếp theo, chúng ta có thể tạo Factory Dog và Factory Horse sử dụng Generator AnimalFactory

dogFactory := AnimalFactory{
  species: "dog",
  houseName: "kennel"
}
dog := dogFactory.NewAnimal(2)
kennel := dogFactory.NewHouse(3)

horseFactory := AnimalFactory{
  species: "horse",
  houseName: "stable"
}
horse := horseFactory.NewAnimal(12)
stable := horseFactory.NewHouse(30)

Hàm Factory

Chúng ta có thể sử dụng hàm Factory để tạo ra các Factory với một vài mặc định được tích hợp sẵn. Ví dụ chúng ta muốn tạo một Baby Factory và Teenager Factory thay vì tạo một Person Factory chung, chúng ta sử dụng hàm Generator

type Person struct {
	name string
	age int
}

func NewPersonFactory(age int) func(name string) Person {
	return func(name string) Person {
		return Person{
			name: name,
			age: age,
		}
	}
}

Chúng ta sử dụng hàm này để tạo các Factory với age mặc định được tích hợp sẵn

newBaby := NewPersonFactory(1)
baby := newBaby("john")

newTeenager := NewPersonFactory(16)
teen := newTeenager("jill")

Thuộc tính age được trừu tượng hoá trong đóng gói NewPersonFactory

Khi nào không nên sử dụng Factory

Factory có thể sử dụng một cách lạm dụng. Nếu bạn đang khởi tạo một struct, giá trị mặc định của Go cũng phù hợp trong hầu hết các trường hợp, thì Factory không thực sự hữu ích. Giả sử nếu bạn muốn tạo một counter

type Counter struct {
  count int
}

func main(){
  c := Counter{}
}

Giá trị mặc định của c.count là 0, về cơ bản là chấp nhận được. Tạo một hàm Factory sẽ không mang lại nhiều giá trị

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.