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 (
BrandA VehicleBrand = iota
BrandB
)
Abstract vehicles
Each of our cars implements the AbstractCar interface, which has only one method: Description() which returns a string description of the vehicle. It looks like this:
type AbstractCar interface {
Description() string
}
We do something similar for the AbstractBike interface:
type AbstractBike interface {
Description() string
}
Concrete cars and brand enums
Now it is time to define our brands:
type VehicleBrand int
const (
BrandA VehicleBrand = iota
BrandB
)
This is a way to emulate enums in Go. The ‘iota‘ keyword is used to denote successive integer constants. Note that we also define a special type for our VehicleBrand.
Now we can define our first car type:
type BrandACar struct {
color string
make string
}
func NewBrandACar(color string) *BrandACar {
return &BrandACar{color: color, make: "BrandA"}
}
func (c *BrandACar) Description() string {
return fmt.Sprintf("Brand A Car - Color: %s, Make: %s", c.color, c.make)
}
Some notes:
- In our simplified example, a car has a color and a make i.e. the brand.
- We define a utility function to construct a car of Brand A with the specified color.
- Also, we implement the
AbstractCar interface. The Description()method returns a desciption of the car.
The BrandABike follows the same pattern. The only difference is, that we do not specify the colors but the number of wheels:
type BrandABike struct {
numberOfWheels int
make string
}
func NewBrandABike(numberOfWheels int) *BrandABike {
return &BrandABike{numberOfWheels: numberOfWheels, make: "BrandA"}
}
func (b *BrandABike) Description() string {
return fmt.Sprintf("Brand A Bike - Wheels: %d, Make: %s", b.numberOfWheels, b.make)
}
For brand B we do the same:
type BrandBCar struct {
color string
make string
}
func NewBrandBCar(color string) *BrandBCar {
return &BrandBCar{color: color, make: "BrandB"}
}
func (c *BrandBCar) Description() string {
return fmt.Sprintf("Brand B Car - Color: %s, Make: %s", c.color, c.make)
}
type BrandBBike struct {
numberOfWheels int
make string
}
func NewBrandBBike(numberOfWheels int) *BrandBBike {
return &BrandBBike{numberOfWheels: numberOfWheels, make: "BrandB"}
}
func (b *BrandBBike) Description() string {
return fmt.Sprintf("Brand B Bike - Wheels: %d, Make: %s", b.numberOfWheels, b.make)
}
Creating the factory
Now create an VehicleCreator interface like this:
type VehicleCreator interface {
createCar(brand VehicleBrand, color string) (AbstractCar, error)
createBike(brand VehicleBrand, numberOfWheels int) (AbstractBike, error)
}
Our VehicleCreatorImpl struct can only construct cars and bikes. Here is its definition:
type VehicleCreatorImpl struct{}
func (vc *VehicleCreatorImpl) createCar(brand VehicleBrand, color string) (AbstractCar, error) {
switch brand {
case BrandA:
return NewBrandACar(color), nil
case BrandB:
return NewBrandBCar(color), nil
default:
return nil, errors.New("unknown vehicle brand for car")
}
}
func (vc *VehicleCreatorImpl) createBike(brand VehicleBrand, numberOfWheels int) (AbstractBike, error) {
switch brand {
case BrandA:
return NewBrandABike(numberOfWheels), nil
case BrandB:
return NewBrandBBike(numberOfWheels), nil
default:
return nil, errors.New("unknown vehicle brand for bike")
}
}
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 VehicleCreator
creator = &VehicleCreatorImpl{}
carA, err := creator.createCar(BrandA, "Red")
if err != nil {
fmt.Printf("Error creating car: %v\n", err)
return
}
bikeB, err := creator.createBike(BrandB, 2)
if err != nil {
fmt.Printf("Error creating bike: %v\n", err)
return
}
fmt.Println(carA.Description())
fmt.Println(bikeB.Description())
}
A breakdown of this code:
- We declare creator to implement the
VehicleCreatorinterface - Next we create the concrete class, and we create a car and a bike and print out their descriptions. If any errors occur they are handled appropiately.
Some possible optimizations of this pattern could be that we cache instances of the different factories, instead of creating them on the fly.
Conclusion
The Abstract Factory pattern provides a clean and scalable way to manage the creation of related objects without tightly coupling your code to specific implementations. In this Go example, we saw how abstract interfaces (AbstractCar, AbstractBike) and concrete types (BrandACar, BrandBBike, etc.) work together through a factory (VehicleCreatorImpl) to produce objects in a flexible, extensible way.
This approach makes it easy to introduce new brands or vehicle types without altering existing code — you simply add new concrete classes and extend the factory. It’s a great demonstration of how the Abstract Factory pattern helps maintain clean separation of concerns while keeping your codebase organized and future-proof.




