Introduction
In this article I discussed the implementation of the Abstract Factory pattern. The Factory Method is simply an extension of that pattern. Creating an object can sometimes be complex. A factory-method can abstract this complexity away, and let subclasses or in our case interface implementations decide which objects to create.
It looks like this:
The ConcreteCreator uses an Abstract Factory to build the object. The AbstractCreator just creates an object which implements the Product interface.
It will all become clearer in the code. The code can be found here.
Implementation in Go
If you have cloned the source code, change the imports to look like this:
import (
"errors"
"fmt"
)
Now just below that, add this:
type VehicleBrand int
const (
BrandVolkswagen VehicleBrand = iota
BrandRenault
)
This is a way to emulate enums in Go. The ‘iota‘ keyword is used to denote successive integer constants.
Now go down in the file to create an IVehicleCreator interface like this:
type IVehicleCreator interface {
createCar(brand VehicleBrand, color string) (AbstractCar, error)
createBike(brand VehicleBrand, numberOfWheels int16) (AbstractBike, error)
}
Our VehicleCreator struct can only construct cars and bikes. Here is its definition:
type VehicleCreator struct{}
func (vc VehicleCreator) createCar(brand VehicleBrand, color string) (AbstractCar, error) {
switch brand {
case BrandVolkswagen:
{
factory := VolkswagenFactory{}
return factory.createCar(color), nil
}
case BrandRenault:
{
factory := RenaultFactory{}
return factory.createCar(color), nil
}
default:
return nil, errors.New("unknown brand")
}
}
func (vc VehicleCreator) createBike(brand VehicleBrand, numberOfWheels int16) (AbstractBike, error) {
switch brand {
case BrandVolkswagen:
{
factory := VolkswagenFactory{}
return factory.createBike(numberOfWheels), nil
}
case BrandRenault:
{
factory := RenaultFactory{}
return factory.createBike(numberOfWheels), nil
}
default:
return nil, errors.New("unknown brand")
}
}
A few things to note:
- Both methods have a VehicleBrand type parameter, to denote that we are talking about the brand. I did not want to use magic strings because of the possible typos. In case a brand is passed on that is not available, an error is returned.
- The returntype is AbstractCar,error or AbstractBike,error. This is in case that for some reason an object could not be created.
Testing it
The testing code looks a bit different now:
func main() {
var creator IVehicleCreator
creator = VehicleCreator{}
car, carError := creator.createCar(BrandVolkswagen, "red")
if carError != nil {
fmt.Println("Could not create Volkswagen car")
return
}
fmt.Println(car.GetDescription())
nextBike, nextError := creator.createBike(BrandRenault, 3)
if nextError != nil {
fmt.Println("Could not create car: ", nextError)
return
}
fmt.Println(nextBike.GetDescription())
}
A breakdown of this code:
- We declare creator to implement the IVehicleCreator interface
- Next we create the concrete class, and we create a car and a bike.
Some possible optimizations of this pattern could be that we cache instances of the different factories, instead of creating them on the fly.
Conclusion
Implementing this pattern was easy, also because I had already implemented an Abstract Factory, on which I could build this code.
As you can see, abstracting away object construction can safe time. Also this code is possibly more testable and maintainable than just having an abstract factory.
Also notice that we return and pass interface types and not concrete types, which makes it even more flexible and extendible.