Abstract Factory Pattern
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 88 of 107.
The Abstract Factory pattern extends the Factory pattern by creating families of related objects without specifying their concrete types. Instead of a single factory function, you define a factory interface that multiple concrete factories implement, each producing a consistent set of related objects.
Consider a UI toolkit that needs to create buttons and checkboxes. Different operating systems require different implementations, but the components must work together consistently:
type Button interface {
Render() string
}
type Checkbox interface {
Check() string
}
// Abstract factory interface
type GUIFactory interface {
CreateButton() Button
CreateCheckbox() Checkbox
}Each concrete factory produces a family of related components:
// Windows family
type WindowsButton struct{}
func (w WindowsButton) Render() string { return "Windows Button" }
type WindowsCheckbox struct{}
func (w WindowsCheckbox) Check() string { return "Windows Checkbox" }
type WindowsFactory struct{}
func (w WindowsFactory) CreateButton() Button { return WindowsButton{} }
func (w WindowsFactory) CreateCheckbox() Checkbox { return WindowsCheckbox{} }
// Mac family
type MacButton struct{}
func (m MacButton) Render() string { return "Mac Button" }
type MacCheckbox struct{}
func (m MacCheckbox) Check() string { return "Mac Checkbox" }
type MacFactory struct{}
func (m MacFactory) CreateButton() Button { return MacButton{} }
func (m MacFactory) CreateCheckbox() Checkbox { return MacCheckbox{} }Client code works with the factory interface, remaining independent of concrete implementations:
func BuildUI(factory GUIFactory) {
button := factory.CreateButton()
checkbox := factory.CreateCheckbox()
fmt.Println(button.Render(), checkbox.Check())
}Use Abstract Factory when your system needs to work with multiple families of related products and you want to ensure components from the same family are used together. It's more complex than a simple Factory, so only reach for it when you genuinely need to manage product families.
Challenge
EasyLet's build a furniture store system using the Abstract Factory pattern! You'll create factories that produce families of related furniture items—each factory produces a consistent style (Modern or Vintage), ensuring that chairs and tables from the same factory match aesthetically.
You'll organize your code across three files:
furniture.go: Define your product interfaces and concrete furniture types.Create two product interfaces:
Chairwith a methodSitOn() stringTablewith a methodPlaceItems() string
Implement two families of furniture:
- Modern family:
ModernChairreturnsSitting on a sleek modern chairandModernTablereturnsPlacing items on a glass modern table - Vintage family:
VintageChairreturnsSitting on an ornate vintage chairandVintageTablereturnsPlacing items on a wooden vintage table
factory.go: Define your abstract factory interface and concrete factories.Create a
FurnitureFactoryinterface with two methods:CreateChair() ChairCreateTable() Table
Implement two concrete factories:
ModernFactorythat producesModernChairandModernTableVintageFactorythat producesVintageChairandVintageTable
Also create a function
GetFactory(style string) FurnitureFactorythat returns the appropriate factory based on the style (modernorvintage). Default toModernFactoryfor unrecognized styles.main.go: Use your factories to furnish rooms.Create a function
FurnishRoom(factory FurnitureFactory) stringthat uses the factory to create a chair and table, then returns a formatted string showing both actions on separate lines.Read a style input, get the appropriate factory, furnish a room, and print the result.
The following inputs will be provided:
- Line 1: Furniture style (
modern,vintage, or something else)
For example, given:
modernYour output should be:
Sitting on a sleek modern chair
Placing items on a glass modern tableAnd given:
vintageYour output should be:
Sitting on an ornate vintage chair
Placing items on a wooden vintage tableAnd given:
rusticYour output should be:
Sitting on a sleek modern chair
Placing items on a glass modern tableNotice how the FurnishRoom function works with any factory through the interface—it doesn't know whether it's creating modern or vintage furniture. This ensures that all furniture in a room comes from the same family, maintaining a consistent style!
Cheat sheet
The Abstract Factory pattern creates families of related objects without specifying their concrete types. It defines a factory interface that multiple concrete factories implement, each producing a consistent set of related objects.
Structure
Define product interfaces for each type of object:
type Button interface {
Render() string
}
type Checkbox interface {
Check() string
}Create an abstract factory interface that declares methods for creating each product type:
type GUIFactory interface {
CreateButton() Button
CreateCheckbox() Checkbox
}Concrete Implementations
Implement concrete product families where each family has consistent styling or behavior:
// Windows family
type WindowsButton struct{}
func (w WindowsButton) Render() string { return "Windows Button" }
type WindowsCheckbox struct{}
func (w WindowsCheckbox) Check() string { return "Windows Checkbox" }
type WindowsFactory struct{}
func (w WindowsFactory) CreateButton() Button { return WindowsButton{} }
func (w WindowsFactory) CreateCheckbox() Checkbox { return WindowsCheckbox{} }
// Mac family
type MacButton struct{}
func (m MacButton) Render() string { return "Mac Button" }
type MacCheckbox struct{}
func (m MacCheckbox) Check() string { return "Mac Checkbox" }
type MacFactory struct{}
func (m MacFactory) CreateButton() Button { return MacButton{} }
func (m MacFactory) CreateCheckbox() Checkbox { return MacCheckbox{} }Client Code
Client code works with the factory interface, remaining independent of concrete implementations:
func BuildUI(factory GUIFactory) {
button := factory.CreateButton()
checkbox := factory.CreateCheckbox()
fmt.Println(button.Render(), checkbox.Check())
}When to Use
Use Abstract Factory when your system needs to work with multiple families of related products and you want to ensure components from the same family are used together. It's more complex than a simple Factory, so only use it when you genuinely need to manage product families.
Try it yourself
package main
import "fmt"
// FurnishRoom uses a factory to create furniture and returns the result
// TODO: Implement this function
// - Use the factory to create a chair and a table
// - Return a string with SitOn() result on first line and PlaceItems() result on second line
func FurnishRoom(factory FurnitureFactory) string {
// TODO: Create chair and table using the factory
// TODO: Return formatted string with both actions on separate lines
return ""
}
func main() {
var style string
fmt.Scanln(&style)
// TODO: Get the appropriate factory based on style
// TODO: Furnish a room using the factory
// TODO: Print the result
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection13Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternAbstract Factory PatternObserver PatternStrategy Pattern2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool