I. Abstract Factory Pattern — Creational Pattern
The Abstract Factory pattern provides an interface for creating families of related objects without specifying their concrete classes. It is a "super-factory" — a factory of factories — that groups together a set of individual factories that have a common theme.
Whereas the Factory Method pattern creates one type of product, Abstract Factory creates multiple related products via a unified interface. The client code never needs to know which concrete factory is in use — it only talks to the VehicleFactory interface.
When to use it:
- You need to create families of related objects that must work together
- You want to isolate concrete product classes from the client
- You need to switch between product families at runtime
II. Real-world Example
Imagine a vehicle shop that sells two kinds of vehicles: bicycles and motorbikes. Each family has multiple variants:
- Bicycle family:
NormalBicycle(2 wheels, 1 seat, road-ready) andSportBicycle(1 wheel, 1 seat, trick riding)
- Motorbike family:
Motorbike125CC(2 wheels, 2 seats, 125cc engine) andMotorbike150CC(2 wheels, 2 seats, 150cc engine)
The client calls BuildFactory(BicycleFactoryType) or BuildFactory(MotorbikeFactoryType) to get the right factory, then calls factory.NewVehicle(variantType) to produce the specific product — without ever importing a concrete struct.

III. Implementation
Vehicle interface
// vehicle.go
package abstractfactory
const (
NormalBicycleType = iota + 1
SportBicycleType
)
const (
Motorbike125CCType = iota + 1
Motorbike150CCType
)
type Vehicle interface {
NumWheels() int
NumSeats() int
}VehicleFactory interface
// vehicle_factory.go
package abstractfactory
const (
BicycleFactoryType = iota + 1
MotorbikeFactoryType
)
type VehicleFactory interface {
NewVehicle(v int) (Vehicle, error)
}
func BuildFactory(f int) (VehicleFactory, error) {
switch f {
case BicycleFactoryType:
return new(bicycleFactory), nil
case MotorbikeFactoryType:
return new(motorbikeFactory), nil
default:
return nil, fmt.Errorf("factory with id %d not found", f)
}
}BicycleFactory
// bicycle_factory.go
package abstractfactory
type bicycleFactory struct{}
func (b *bicycleFactory) NewVehicle(v int) (Vehicle, error) {
switch v {
case NormalBicycleType:
return &normalBicycle{}, nil
case SportBicycleType:
return &sportBicycle{}, nil
default:
return nil, fmt.Errorf("vehicle with id %d not found", v)
}
}MotorbikeFactory
// motorbike_factory.go
package abstractfactory
type motorbikeFactory struct{}
func (m *motorbikeFactory) NewVehicle(v int) (Vehicle, error) {
switch v {
case Motorbike125CCType:
return &motorbike{wheelSize: 125}, nil
case Motorbike150CCType:
return &motorbike{wheelSize: 150}, nil
default:
return nil, fmt.Errorf("vehicle with id %d not found", v)
}
}Concrete products
// normal_bicycle.go
package abstractfactory
type normalBicycle struct{}
func (n *normalBicycle) NumWheels() int { return 2 }
func (n *normalBicycle) NumSeats() int { return 1 }
func (n *normalBicycle) GetType() string { return "Normal" }
// sport_bicycle.go
type sportBicycle struct{}
func (s *sportBicycle) NumWheels() int { return 1 }
func (s *sportBicycle) NumSeats() int { return 1 }
func (s *sportBicycle) GetType() string { return "Sport" }
// motorbike.go
type motorbike struct{ wheelSize int }
func (m *motorbike) NumWheels() int { return 2 }
func (m *motorbike) NumSeats() int { return 2 }
func (m *motorbike) GetCC() string { return fmt.Sprintf("%dcc", m.wheelSize) }IV. Explain the Example Above
Let's trace the execution step by step:
Step 1 — Choose a factory:
factory, _ := BuildFactory(BicycleFactoryType)
// Returns *bicycleFactory (implements VehicleFactory)Step 2 — Create a product from the factory:
vehicle, _ := factory.NewVehicle(NormalBicycleType)
// Returns *normalBicycle (implements Vehicle)Step 3 — Use the product through the interface:
fmt.Printf("Wheels: %d, Seats: %d\n", vehicle.NumWheels(), vehicle.NumSeats())Output:
Wheels: 2, Seats: 1Now switch to a motorbike family — the client code doesn't change:
factory, _ := BuildFactory(MotorbikeFactoryType)
vehicle, _ := factory.NewVehicle(Motorbike150CCType)
fmt.Printf("Wheels: %d, Seats: %d\n", vehicle.NumWheels(), vehicle.NumSeats())Output:
Wheels: 2, Seats: 2This is the power of Abstract Factory: swapping the entire product family by changing only BuildFactory()'s argument. The rest of the code is untouched.
Error handling is consistent across all factories:
_, err := BuildFactory(99)
// err: "factory with id 99 not found"
factory, _ := BuildFactory(BicycleFactoryType)
_, err = factory.NewVehicle(99)
// err: "vehicle with id 99 not found"V. Conclusion
The Abstract Factory pattern shines when your system needs to work with multiple families of related products, and you want to guarantee consistency within a family. In our example, a BicycleFactory will never accidentally create a Motorbike — each factory only knows its own product line.
Compared to Factory Method:
- Factory Method creates one product via subclass overriding
- Abstract Factory creates a family of products via a factory interface
Trade-offs to keep in mind:
- Adding a new product type (e.g.
Tricycle) requires updating all factory implementations — this is by design (Open/Closed Principle applies to new families, not new types)
- The indirection adds a layer of abstraction — worthwhile for large systems, overkill for simple ones
As with all design patterns, no pattern is universally superior. Use Abstract Factory when the object family grouping is a meaningful domain concept, not just a convenience wrapper.
VI. References
- Book: Go Design Patterns by Mario Castro Contreras

