+22

Golang Design Patterns - Abstract Factory. Hơn cả một Factory

I. Abstract Factory - Creational Pattern

Thiết kế chưa có tên (2).png

Nếu chúng ta đã biết được mẫu thiết kế Factory là như thế nào, thì hôm nay mình xin giới thiệu với các bạn design pattern bao quát hơn nữa, chính là Absract Factory.

Mục đích chính của mẫu thiết kế này là gom nhóm các Factory thành một Factory lớn, thứ có thể hoán đổi và mở rộng một cách dễ dàng hơn. Thường trong giai đoạn đầu của việc plan, define về chức năng mà các Factory có thể có, việc chúng ta thao tác và chỉ quan tâm đến các factories và abstract factories sẽ là một hướng tiếp cận dễ dàng hơn so với việc đợi define đầy đủ chi tiết cho chúng. Nhưng để triển khai pattern này, chúng ta cũng cần quan tâm những thứ mà mình sẽ nói trong bài viết sau nhé 😄

II. Abstract Factory mang lại cho developers những gì?

Pattern này giúp chúng ta gom nhóm các đối tượng liên quan khi số lượng đối tượng có thể tăng lên mà ta không thể kiểm soát trong giai đoạn phát triển, bằng cách tạo ra một unique point, nơi có thể tạo ra tất cả các đối tượng đó:

  • Tạo ra một lớp mới đóng gói các Factory Method và trả về một common interface cho toàn bộ fatories.
  • Gom nhóm các common factories vào một super Factory, hay còn được gọi là Factory của những Factories.

III. Ví dụ thực tế

Chúng ta sẽ quay lại với ví dụ trong bài Builder Design Pattern, nhưng có một vài yêu cầu đặc biệt hơn, chúng ta đến một cửa hàng bán xe để mua 1 chiếc xe máy và 1 chiếc xe đạp. Cửa hàng này có các loại xe sau:

  • Xe đạp có 2 loại: xe đạp bình thường và xe đạp thể thao
  • Xe máy có 2 loại: xe 125cc và 150cc

Để triển khai Abstract Factory dựa trên đối tượng Vehicle của mẫu Factory Method trước đó, chúng ta cần tuân thủ các nguyên tắc sau:

  • Chúng ta luôn tạo Vehicle bằng cách sử dụng factory được trả về từ abstract factory
  • Một đối tượng xe luôn là 1 trong 2 đối tượng Bicycle (implement Vehicle + Bicycle) hoặc Motorbike (implement interface Vehicle + Motorbike)

IV. Implementation

Theo đề bài, chúng ta cần phải tạo những đối tượng như sau:

  • Vehicle: là một interface mà tất cả các đối tượng factory cần phải implement:
    • Bicycle: Một interface bao gồm 2 loại xe đạp NormalBicycle - xe đạp thường và SportBicycle - xe đạp thể thao
    • Motorbike: interface của xe máy gồm xe máy Motor125CC và Motor150CC

  • VehicleFactory: là interface (abstract interface mà chúng ta đang quan tâm) để tạo ra các factories triển khai phương thức của nó:
    • BicycleFactory: là factory implement VehicleFactory interface và trả về đối tượng vehicle, đối tượng này bắt buộc phải implement các phương thức của interface Vehicle và Bicycle
    • MotorbikeFactory: tương tự như trên, chỉ khác là các đối tượng vehicle này phải triển khai các phương thức từ interface Vehicle và Motorbike


Bắt đầu với Golang thôi nào ^^

Chúng ta sẽ tạo các entity ở trên bằng những file riêng biệt. Đầu tiên là Vehicle interface trong file vehicle.go

package abstract_factory

type Vehicle interface {
	NumWheels() int
	NumSeats() int
}

Bicycle interface và MotorBike interface sẽ được tạo trong file bicyle.go và motorbike.go, tương ứng như sau:

package abstract_factory

type Bicycle interface {
	GetType() int
}
package abstract_factory

type Motorbike interface {
	GetCC() string
}

Chúng ta có một interface cuối cùng, nơi mà tất cả các factory đều phải triển khai đó là VehicleFactory trong file vehicle_factory.go, interface này cung cấp 1 method duy nhất (được xem như chức năng của cửa hàng bán xe là lấy xe giao cho khách 😄)

package abstract_factory

type VehicleFactory interface {
	NewVehicle(v int) (Vehicle, error)
}

Bây giờ tới gian đoạn triển khai các đối tượng nhỏ hơn, ta bắt đầu với BicycleFactory và MotorbikeFactory nhé, vì đoạn này implement như mẫu Factory trong bài trước nên các bạn đọc qua sẽ hiểu, mình không nói chi tiết nữa. Với BicycleFactory:

package abstract_factory

import (
	"errors"
	"fmt"
)

const (
	NormalBicycleType = 1
	SportBicycleType  = 2
)

type BicycleFactory struct{}

func (c *BicycleFactory) NewVehicle(v int) (Vehicle, error) {
	switch v {
	case NormalBicycleType:
		return new(NormalBicycle), nil
	case SportBicycleType:
		return new(SportBicycle), nil
	default:
		err := fmt.Sprintf("Vehicle of type %d not recognized\n", v)
		return nil, errors.New(err)
	}
}

package abstract_factory

type NormalBicycle struct{}

func (*NormalBicycle) GetType() string {
	return "Normal Bicycle"
}

func (*NormalBicycle) NumWheels() int {
	return 2
}

func (*NormalBicycle) NumSeats() int {
	return 1
}

package abstract_factory

type SportBicycle struct{}

func (*SportBicycle) GetType() string {
	return "Sport Bicycle"
}

func (*SportBicycle) NumWheels() int {
	return 1
}

func (*SportBicycle) NumSeats() int {
	return 1
}

Và MotorbikeFactory:

package abstract_factory

import (
	"errors"
	"fmt"
)

const (
	Motorbike125CCType = 1
	Motorbike150CCType = 2
)

type MotorbikeFactory struct{}

func (c *MotorbikeFactory) NewVehicle(v int) (Vehicle, error) {
	switch v {
	case Motorbike125CCType:
		return new(Motorbike125CC), nil
	case Motorbike150CCType:
		return new(Motorbike150CC), nil
	default:
		err := fmt.Sprintf("Vehicle of type %d not recognized\n", v)
		return nil, errors.New(err)
	}
}



package abstract_factory

type Motorbike125CC struct{}

func (*Motorbike125CC) GetCC() string {
	return "125cc"
}

func (*Motorbike125CC) NumWheels() int {
	return 2
}

func (*Motorbike125CC) NumSeats() int {
	return 2
}

package abstract_factory

type Motorbike150CC struct{}

func (*Motorbike150CC) GetCC() string {
	return "150cc"
}

func (*Motorbike150CC) NumWheels() int {
	return 2
}

func (*Motorbike150CC) NumSeats() int {
	return 2
}

Và cuối cùng, chúng ta cần abstract các factory trên, và tạo ra một unique point như đầu bài viết mình có nhắc đến, phục vụ cho việc tạo ra cách đối tượng vehicle. Chúng ta triển khai function này trong file vehicle_factory.go đã được tạo phía trên luôn nhé:

package abstract_factory

import (
	"errors"
	"fmt"
)

type VehicleFactory interface {
	NewVehicle(v int) (Vehicle, error)
}

const (
	BicycleFactoryType   = 1
	MotorbikeFactoryType = 2
)

func BuildFactory(f int) (VehicleFactory, error) {
	switch f {
	case BicycleFactoryType:
		return new(BicycleFactory), nil
	case MotorbikeFactoryType:
		return new(MotorbikeFactory), nil
	default:
		err := fmt.Sprintf("Factory with id %d not recognized\n", f)
		return nil, errors.New(err)
	}

}

Có đầy đủ tất cả rồi, chúng ta chạy chương trình trong main.go:

func main() {
	/*
		Example Abstract Factory
	*/
	fmt.Println("*** Example Abstract Factory ***")

	bicycleFactory, err := abstract_factory.BuildFactory(abstract_factory.BicycleFactoryType)
	if err != nil {
		fmt.Println("Error: ", err)
	}

	sportBicycle, err := bicycleFactory.NewVehicle(abstract_factory.SportBicycleType)
	if err != nil {
		fmt.Println("Error: ", err)
	}

	fmt.Println("Sport Bicycle:")
	fmt.Printf("Vehicle has %d wheels, %d seats.\n", sportBicycle.NumWheels(), sportBicycle.NumSeats())

	fmt.Print("*** End of Abstract Factory ***\n\n\n")

}

Kết quả: image.png

V. Lời kết

Qua bài viết của mình, các bạn đã hiểu được cách tạo ra factory từ những factories rồi, mẫu thiết kế này thường được dùng nhiều trong các ứng dụng, thư viện đa nên tảng như GUI libraries. Lấy ví dụ đơn giản nhất là button, một đối tượng rất chung, và một button factory giúp bạn tạo ra các factory trên các OS khác nhau như Microsoft Windows buttons hay Mac OS X buttons. Chúng ta không cần quan tâm phần triển khai cụ thể trên từng platform, hệ điều hành,... Công việc của chúng ta chỉ cần định nghĩa một vài action đặc biệt phải có của một button mà thôi.

Việc tạo ra các đối tượng, có thể tiếp cận bằng nhiều phương pháp khác nhau. Như ở bài viết Buider Design Pattern, mình tạo ra Bicycle và MotorBike từ 1 factory duy nhất, nhưng ở bài viết này, mình đã mở rộng nó ra, và chúng ta có thể thấy được việc customize những đối tượng mới thêm vào sẽ dễ dàng hơn rất nhiều. Tuy nhiên, không có mẫu thiết kế nào là tối ưu hơn mẫu thiết kế nào cả, tất cả là phụ thuộc và business, vào chính bạn ^^

Cảm ơn các bạn đã xem bài viết.

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í