Chào các bạn, trong bài viết trước chúng ta đã tìm hiểu về Factory Method Pattern. Trong bài này chúng ta tiếp tục tìm hiểu một Pattern khác trong nhóm Creational Design Pattern là Abstract Factory Design Pattern.

Abstact Factory Pattern

Abstract Factory pattern là một trong những Creational pattern. Nó là pattern tạo ra một Super-factory dùng để tạo ra các Factory khác, chúng ta có thể hiểu là Factory của các Factory. Qua đó chúng ta có thể coi Abstract Factory Pattern là một Pattern cấp cao hơn so với Factory Method Pattern.

Để dễ hình dung hơn, hãy tưởng tượng Abstract Factory như là một nhà máy lớn chứa nhiều nhà máy nhỏ, trong các nhà máy đó có những xưởng sản xuất, các xưởng đó tạo ra những sản phẩm khác nhau.

Abstract factory pattern banner

Cài đặt Abstact Factory Pattern

Abstract Factory Pattern bao gồm năm thành phần cơ bản là: Abstract Factory, Concrete Factory, Abstract Product, Concrete Product và Client.

  • AbstractFactory: Khai báo dạng interface hoặc abstract class chứa các phương thức để tạo ra các đối tượng abstract.
  • ConcreteFactory: Xây dựng, cài đặt các phương thức tạo các đối tượng cụ thể.
  • AbstractProduct: Khai báo dạng interface hoặc abstract class để định nghĩa đối tượng abstract.
  • Product: Cài đặt của các đối tượng cụ thể, cài đặt các phương thức được quy định tại AbstractProduct.
  • Client: là đối tượng sử dụng AbstractFactory và các AbstractProduct.

Hình minh họa cách triển khai Abstract Factory Pattern

Hình minh họa cách triển khai Abstract Factory Pattern

Ví dụ demo

Ví dụ: Một công ty chuyên sản xuất xe (Vehical) bao gồm 2 loại phương tiện: xe hơi (Car) và xe máy (Motobike).

  • Car bao gồm 2 loại xe: familiar carluxury car.

  • Motobike bao gồm 2 loại xe: sport motobikecruise motobike

Vì quy trình sản xuất cho từng loại phương tiện là khác nhau. Nên công ty tách ra là nhà máy (Factory): 1 cho sản xuất xe ô tô (CarFactory), 1 cho sản xuất xe máy (BicycleFactory). Khi khách hàng cần mua loại xe nào thì khách hàng (Client) chỉ cần đến cửa hàng để mua (VehicleFactory). Khi đó ứng với từng phương tiện sẽ được chuyển về phân xưởng tương ứng để sản xuất

Tiến hành cài đặt

Trong ví dụ này chúng ta có những thực thể sau

  • Vehicle : Interface mà tất cả các đối tượng trong trong các factory phải triển khai

    • Motorbike : Interface dành cho 2 loại xe motobike là : sport và cruise (Concrete product)
    • Car : Interface dành cho 2 loại xe car là : familiar và luxury (Concrete product)
  • VehicleFactory : Đây là interface (Abstract Factory) trả về các factory implement VehicleFactory interface

    • Motorbike Factory : Một factory implement interface VehicleFactory và trả lại phương tiện implement 2 interface là VehicleMotorbike

    • Car Factory : Một factory implement interface VehicleFactory và trả lại phương tiện implement 2 interface là VehicleCar

Để rõ ràng trong quá trình triển khai, chúng ta sẽ tách từng thực thể thành các file khác nhau. Chúng ta sẽ bắt đầu với interface Vehicle trong file vehicle.go

// file vehicle.go
package vehicle

type Vehicle interface {
    GetWheels() int
    GetSeats() int
}

Interface car và motobike trong car.gomotobike.go

// car.go
package vehicle

type Car interface {
    GetDoors() int
}

// motobile.go
package vehicle

type Motorbike interface {
    GetType() int
}

Tiếp theo chúng ta cần định nghĩa interface mà mỗi factory cần phải implement trong file vehicle_factory.go

// vehicle_factory.go
package vehicle

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

OK, tiếp theo chúng ta sẽ khai báo car factory. CarFactory phải implement VehicleFactory interface đã được định nghĩa trước đó và trả về Vehicles instances

// car_factory.go

package vehicle

import (
    "errors"
    "fmt"
)

const (
    LuxuryCarType   = 1
    FamiliarCarType = 2
)

type CarFactory struct{}

func (c *CarFactory) GetVehicle(v int) (Vehicle, error) {
    switch v {
    case LuxuryCarType:
        return new(LuxuryCar), nil
    case FamiliarCarType:
        return new(FamiliarCar), nil
    default:
        return nil, errors.New(fmt.Sprintf("Vehicle of type %d not recognized\n", v))
    }
}

Ở đây chúng ta có 2 loại xe : luxury car và familiar car. Cho nên CarFactory cần phải trả về đối tượng car implement 2 interface là : VehicleCar, vì vậy chúng ta cần tạo ra 2 concrete product

// luxury_car.go
package vehicle

type FamiliarCar struct{}

func (l *FamiliarCar) GetDoors() int {
    return 5
}
func (l *FamiliarCar) GetWheels() int {
    return 4
}
func (l *FamiliarCar) GetSeats() int {
    return 5
}

// familiar_car.go
package vehicle

type LuxuryCar struct{}

func (l *LuxuryCar) GetDoors() int {
    return 6
}
func (l *LuxuryCar) GetWheels() int {
    return 7
}
func (l *LuxuryCar) GetSeats() int {
    return 8
}

Tương tự như car, bây giờ chúng ta cũng cần tạo ra motobike factory và implement VehicleFactory interface

// motobike_factory.go

package vehicle

import (
    "errors"
    "fmt"
)

const (
    SportMotorbikeType  = 1
    CruiseMotorbikeType = 2
)

type MotorbikeFactory struct{}

func (m *MotorbikeFactory) GetVehicle(v int) (Vehicle, error) {
    switch v {
    case SportMotorbikeType:
        return new(SportMotorbike), nil
    case CruiseMotorbikeType:
        return new(CruiseMotorbike), nil
    default:
        return nil, errors.New(fmt.Sprintf("Vehicle of type %d not recognized\n", v))
    }
}

Đối với phương tiện là motobike chúng ta xác định có 2 loại xe : sport motobike và cruise motobike. Vậy nên chúng ta cần tạo ra 2 concrete product để implement 2 interface là : VehicleCar

// sport_motobike.go
package vehicle

type SportMotorbike struct{}

func (s *SportMotorbike) GetWheels() int {
    return 2
}
func (s *SportMotorbike) GetSeats() int {
    return 1
}
func (s *SportMotorbike) GetType() int {
    return SportMotorbikeType
}

// cruise_motobike.go
package vehicle

type CruiseMotorbike struct{}

func (c *CruiseMotorbike) GetWheels() int {
    return 2
}
func (c *CruiseMotorbike) GetSeats() int {
    return 2
}
func (c *CruiseMotorbike) GetType() int {
    return CruiseMotorbikeType
}

Để hoàn thành thì chúng ta cần chỉnh lại abstract factory lúc đầu trong file vehicle_factory.go để nó tạo và trả về sub-factory tương ứng

// vehicle_factory.go

package vehicle

import (
    "errors"
    "fmt"
)

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

const (
    CarFactoryType       = 1
    MotorbikeFactoryType = 2
)

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

OK, bây giờ trong file main.go (client) chúng ta gọi factory và sản xuất ra các loại phương tiện tương ứng

package main

import (
    "fmt"
    v "abstract-factory-pattern/vehicle"
)

func main() {
    // Car Factory
    carFactory, _ := v.GetVehicleFactory(v.CarFactoryType)
    
    // Luxury car
    luxuryCar, _ := carFactory.GetVehicle(v.LuxuryCarType)

    fmt.Println("Luxury car")
    printDetail(luxuryCar)
    
    // Familiar car
    familiarCar, _ := carFactory.GetVehicle(v.FamiliarCarType)
    fmt.Println("Familiar car")
    printDetail(familiarCar)
    
    // Motobike Factory
    motobikeFactory, _ := v.GetVehicleFactory(v.MotorbikeFactoryType)
    
    // Sport Motobike
    sportMotobike, _ := motobikeFactory.GetVehicle(v.SportMotorbikeType)

    fmt.Println("Sport Motobike")
    printDetail(sportMotobike)

    // Cruise Motobike
    cruiseMotobike, _ := motobikeFactory.GetVehicle(v.CruiseMotorbikeType)

    fmt.Println("Cruise Motobike")
    printDetail(cruiseMotobike)
}

func printDetail(v v.Vehicle) {
    fmt.Printf("Seats : %d\n", v.GetSeats())
    fmt.Printf("Wheels : %d\n\n", v.GetWheels())
}

Kết quả

Luxury car
Seats : 8
Wheels : 7

Familiar car
Seats : 5
Wheels : 4

Sport Motobike
Seats : 1
Wheels : 2

Cruise Motobike
Seats : 2
Wheels : 2

Ưu và nhược điểm của Abstract Factory Pattern

Ưu điểm

  • Abstract Factory Pattern giúp đảm bảo rằng các product mà chúng ta nhận được từ một factory đều tương thích với nhau
  • Abstract Factory Pattern giúp hạn chế sự phụ thuộc giữa creator và concrete products.
  • Abstract Factory Pattern giúp gom các đoạn code tạo ra product vào một nơi trong chương trình, nhờ đó giúp dễ theo dõi và thao tác.
  • Với Abstract Factory Pattern, chúng ta có thể thoải mái thêm nhiều loại product mới vào chương trình mà không làm thay đổi các đoạn code nền tảng đã có trước đó.

Nhược điểm

  • Code có thể trở nên nhiều hơn và phức tạp hơn do đòi hỏi phải sử dụng nhiều class mới có thể cài đặt được pattern này.