Design Pattern: Abstract Factory Pattern in Go

Abstract factory pattern generates object factories, which in turn will generate item objects. Each factory is responsible for generating a certain group of item objects.

NOTES

Abstract factory pattern works as a factory of the Factory pattern, so before learning the details of the Abstract factory pattern make sure to have a clear idea about the Factory pattern.

NOTES

In this article, we discuss the implementation of the Abstract Factory Pattern in Go.

See the Abstract Factory in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Abstract Factory Pattern-

Implementation

Use the following steps for the implementation-

  • Define an item interface.
  • Define items (structs) and implement the interface for the items.
  • Define interface for the factories. Declare a method that will generate and return an item object. In the following example, we have named it “GetItem”.
  • Define factory structs and implement the factory interface.
  • Define a producer struct, and include a method that generates a factory and returns that. In the following example, the function name is “GetFactory”.
  • For using the pattern, create an object of the factory, using the producer. Then use the factory object to produce item object.

Here is a simple Abstract factory implementation in TypeScript. This is not any specific example, just demo/sample code.

// Abstract Factory Pattern

package main

import (
	"errors"
	"fmt"
)

// ---  Items definition  start ---

// Item interface
type Item interface {
	Operation1()
}

// First item of type1
type Type1Item1 struct {}

func NewType1Item1() (type1Item1 *Type1Item1) {
	type1Item1 = &Type1Item1{}
	return
}

func (type1Item1 *Type1Item1) Operation1() {
	fmt.Println("Executing: Type 1 - Item 1 - Operation 1")
}

// Second item of type1
type Type1Item2 struct {}

func NewType1Item2() (type1Item1 *Type1Item1) {
	type1Item1 = &Type1Item1{}
	return
}

func (type1Item1 *Type1Item1) Operation2() {
	fmt.Println("Executing: Type 1 - Item 2 - Operation 1")
}

// First item of type2
type Type2Item1 struct {
}

func NewType2Item1() (type2Item1 *Type2Item1) {
	type2Item1 = &Type2Item1{}
	return
}

func (type2Item1 *Type2Item1) Operation1() {
	fmt.Println("Executing: Type 2 - Item 1 - Operation 1")
}

// Second item of type2
type Type2Item2 struct {
}

func NewType2Item2() (type2Item1 *Type2Item1) {
	type2Item1 = &Type2Item1{}
	return
}

func (type2Item1 *Type2Item1) Operation2() {
	fmt.Println("Executing: Type 2 - Item 2 - Operation 1")
}

// --- Items defnition end ---

// --- Factory defnitions start ---

// Factory interface
type Factory interface {
	GetItem(identifier string) (Item, error)
}

// type1 factory
type  Type1Factory struct {}

func NewType1Factory() (type1Factory *Type1Factory) {
	type1Factory = &Type1Factory{}
	return
}

func (type1Factory *Type1Factory) GetItem(itemIdentifier string) (Item, error) {
	switch itemIdentifier {
	case "item1": return NewType1Item1(), nil
	case "item2": return NewType1Item2(), nil
	default: return nil, errors.New("itemIdentifier not recognized for type1")
	}
}

// type2 factory
type  Type2Factory struct {}

func NewType2Factory() (type2Factory *Type2Factory) {
	type2Factory = &Type2Factory{}
	return
}

func (type2Factory *Type2Factory) GetItem(itemIdentifier string) (Item, error) {
	switch itemIdentifier {
	case "item1": return NewType2Item1(), nil
	case "item2": return NewType2Item2(), nil
	default: return nil, errors.New("itemIdentifier not recognized for type2")
	}
}

// factory producer
type FactoryProducer struct {

}

func NewFactoryProducer() (factoryProdicer *FactoryProducer) {
	factoryProdicer = &FactoryProducer{}
	return
}

func (factoryProducer *FactoryProducer) GetFactory(typeIdentifier string) (Factory, error) {
	switch typeIdentifier {
	case "type1": return NewType1Factory(), nil
	case "type2": return NewType2Factory(), nil
	default: return nil, errors.New("type identifier not recognized")
	}
}

// --- Factory definitions end ---


// --- Demo start ---

func main() {
	factoryProducer := NewFactoryProducer()

	type1Factory, err := factoryProducer.GetFactory("type1")

	if err == nil {
		type1Item1, err := type1Factory.GetItem("item1")

		if err == nil {
			type1Item1.Operation1()
		}
	}

	type2Factory, err := factoryProducer.GetFactory("type2")

	if err == nil {
		type2Item1, err := type2Factory.GetItem("item1")

		if err == nil {
			type2Item1.Operation1()
		}
	}
}

// --- Demo end ---

Above code will generate the following output:

Executing: Type 1 - Item 1 - Operation 1

Executing: Type 2 - Item 1 - Operation 1

Examples

Let’s look at a few examples of Abstract factory pattern implementation in Golang.

Example #1: Transport

In this example, we have 2 groups of transport – 2-wheel and 4-wheel. So, we will create 2 factories and use a producer to generate factory objects.

Transport Interface [Item Interface]

  • Create file “transport.go”.
  • Declare methods – “Start”, “Stop”, and “Repair”.
// transport.go

package main

type Transport interface {
	Start()
	Stop()
	Repair()
}

Bicycle Struct [Item]

  • Create a file named “bicycle.go”.
  • Create struct “Bicycle”.
  • Create method “NewBicycle” which returns a new “Bicycle” object.
  • Implement the “Transport” interface, define “Start”, “Stop”, and “Repair” methods for the struct.
// bicycle.go

package main

import "fmt"

type Bicycle struct {
}

func NewBicycle() (bicycle *Bicycle) {
	bicycle = &Bicycle{}
	return
}

func (bicycle *Bicycle) Start() {
	fmt.Println("Bicycle Started")
}

func (bicycle *Bicycle) Stop() {
	fmt.Println("Bicycle Stopped")
}

func (bicycle *Bicycle) Repair() {
	fmt.Println("Bicycle Repair")
}

Motorcycle Struct [Item]

  • Create a file named “motorcycle.go”.
  • Create struct “Motorcycle”, and implement “Transport” interface for the struct. 
  • Create method “NewMotorcycle” to return a new “Motorcycle” object.
// motorcycle.go

package main

import "fmt"

type Motorcycle struct {
}

func NewMotorcycle() (motorcycle *Motorcycle) {
	motorcycle = &Motorcycle{}
	return
}

func (motorcycle *Motorcycle) Start() {
	fmt.Println("Motorcycle Started")
}

func (motorcycle *Motorcycle) Stop() {
	fmt.Println("Motorcycle Stopped")
}

func (motorcycle *Motorcycle) Repair() {
	fmt.Println("Motorcycle Repair")
}

Car Struct [Item]

  • Create a file named “car.go”.
  • Define “Car” item struct and implement “Transport” interface.
// car.go

package main

import "fmt"

type Car struct {
}

func NewCar() (car *Car) {
	car = &Car{}
	return
}

func (car *Car) Start() {
	fmt.Println("Car Started")
}

func (car *Car) Stop() {
	fmt.Println("Car Stopped")
}

func (car *Car) Repair() {
	fmt.Println("Car Repair")
}

Truck Struct [Item]

  • Create a file named “truck.go”.
  • Define “Truck” struct and implement “Transport” interface for “Truck”.
// truck.go

package main

import "fmt"

type Truck struct {
}

func NewTruck() (truck *Truck) {
	truck = &Truck{}
	return
}

func (truck *Truck) Start() {
	fmt.Println("Truck Started")
}

func (truck *Truck) Stop() {
	fmt.Println("Truck Stopped")
}

func (truck *Truck) Repair() {
	fmt.Println("Truck Repair")
}

Transport Factory Interface

  • Create a file named “abstract_transport-factory.go”.
  • Define an interface named “TransportFactory”. This interface will be used by all factory.
  • Declare a method to generate and return an item object. Here we have named it “GetTransport”.
// transport_factory.go

package main

type TransportFactory interface {
	GetTransport(identifier string) (Transport, error)
}

Two-Wheel Transport Factory

  • Create a file named “two_wheel_transport_factory.go”.
  • Create a struct named “TwoWheelTransportFactory”. This factory is responsible for generating items of 2 wheel vehicles.
  • Create method named “NewTwoWheelTransportFactory”.
  • Implement the factory interface “TransportFactory” by defining the method “GetTransport” for the struct.
// two_wheel_transport_factory.go

package main

import (
	"errors"
	"strings"
)

type TwoWheelTransportFactory struct {
}

func NewTwoWheelTransportFactory() (twoWheelTransportFactory *TwoWheelTransportFactory) {
	twoWheelTransportFactory = &TwoWheelTransportFactory{}
	return
}

func (twoWheelTransportFactory *TwoWheelTransportFactory) GetTransport(identifier string) (Transport, error) {
	identifier = strings.ToLower(identifier)
	
	switch identifier {
		case "bicycle": return NewBicycle(), nil
		case "motorcycle": return NewMotorcycle(), nil
		default: return nil, errors.New("identifier does not match with any defined two wheel transport")
	}
}

Four-Wheel Transport Factory

  • Create a file named “four_wheel_transport_factory.go”.
  • Create struct “FourWheelTransportFactory”.
  • Define method “GetTrasnport” for the factory, as part of “TransportFactory” interface implementation.
// four_wheel_transport_factory.go

package main

import (
	"errors"
	"strings"
)

type FourWheelTransportFactory struct {
}

func NewFourWheelTransportFactory() (fourWheelTransportFactory *FourWheelTransportFactory) {
	fourWheelTransportFactory = &FourWheelTransportFactory{}
	return
}

func (fourWheelTransportFactory *FourWheelTransportFactory) GetTransport(identifier string) (Transport, error) {
	identifier = strings.ToLower(identifier)

	switch identifier {
	case "car":
		return NewCar(), nil
	case "truck":
		return NewTruck(), nil
	default:
		return nil, errors.New("identifier does not match with any defined four wheel transport")
	}
}

Factory Producer

  • Create a file named “transport_factory_producer.go”.
  • Create struct “TransportFactoryProducer”. This is the producer of factory.
  • Define method “GetFactory” which generates factory object and return that.
// transport_factory_producer.go

package main

import "errors"

type TransportFactoryProducer struct {
}

func NewTransportFactoryProducer() (transportFactoryProducer *TransportFactoryProducer) {
	transportFactoryProducer = &TransportFactoryProducer{}
	return
}

func (transportFactoryProducer *TransportFactoryProducer) GetFactory(numberOfWheels int) (TransportFactory, error) {
	switch numberOfWheels {
	case 2:
		return NewTwoWheelTransportFactory(), nil
	case 4:
		return NewFourWheelTransportFactory(), nil
	default:
		return nil, errors.New("number of wheels does not match with any predefined tyep")
	}
}

Demo

  • Create a file named “main.go”.
  • Create a new object of producer and save it in “transportFactoryProducer”.
  • Use method “GetFactory” of the producer to get a factory object.
  • Then use the factory object to get transport item object.
// main.go

package main

func main() {
	transportFactoryProducer := NewTransportFactoryProducer()

	twoWheelFactory, err := transportFactoryProducer.GetFactory(2)

	if err == nil {
		bicycle, err := twoWheelFactory.GetTransport("bicycle")

		if err == nil {
			bicycle.Start()
		}
	}

	fourWheelFactory, err := transportFactoryProducer.GetFactory(4)

	if err == nil {
		truck, err := fourWheelFactory.GetTransport("truck")

		if err == nil {
			truck.Start()
			truck.Stop()
			truck.Repair()
		}
	}
}

Output

Here is the output of the demo code.

Bicycle Started

Truck Started
Truck Stopped
Truck Repair

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Abstract Factory pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.