Design Pattern Series - Abstract Factory Pattern (Golang)
Previous post in series: Factory Pattern
The Abstract Factory Pattern provides an interface for creating groups of related or dependent objects without directly specifying a specific class. Instead of creating objects directly, this pattern uses a factory to generate objects belonging to the same “family”.
For example, suppose you build a UI application for multiple operating systems (Windows, macOS). In that case, the Abstract Factory Pattern allows you to create UI components like a Button or a Window suitable for each operating system, without changing the main logic code.
Main Features
- Abstracting object creation: Clients only work with interfaces, reducing dependency on concrete classes.
- Creating related object groups: Each factory generates a set of objects belonging to the same family, ensuring consistency.
- Flexible: Easily convert or extend object groups at runtime without modifying client code.
Benefit and Use Cases
Benefit
- Ensure consistency: The created objects always belong to the same family, avoiding “mixing” (e.g. using Windows Button with macOS Window).
- Increase abstraction: Client only interacts through interface → easy to maintain, extend, reduce dependencies.
- Reduce complexity: Client does not need to know the details of how to create objects, just need to choose the right factory.
Use Cases
- Build cross-platform UI: Create UI components (Button, Window, Scrollbar) for Windows, macOS, Linux.
- The system has many variations: For example, create product groups such as Engine, Wheel for sedan or SUV.
- Dynamic configuration change: When you need to change the whole group of objects at runtime without editing the code.
Implement Abstract Factory Pattern in Go
For example, we will build a UI system with two types of products: Button and Window, for two operating systems, Windows and macOS.
Step 1: Declare an Interface for the Button and the Window
// products.go
package main
type Button interface {
Render() string
}
type Window interface {
Draw() string
}Explain:
Buttonrequired methodRender().Windowrequired methodDraw().- All concrete products (WindowsButton, MacOSWindow) will implement the above interface.
Step 2: Create concrete products
// windows_button.go
package main
type WindowsButton struct{}
func (b WindowsButton) Render() string {
return "Rendering a Windows button"
}
// macos_button.go
package main
type MacOSButton struct{}
func (b MacOSButton) Render() string {
return "Rendering a macOS button"
}
// windows_window.go
package main
type WindowsWindow struct{}
func (w WindowsWindow) Draw() string {
return "Drawing a Windows window"
}
// macos_window.go
package main
type MacOSWindow struct{}
func (w MacOSWindow) Draw() string {
return "Drawing a macOS window"
}Explain:
WindowsButtonandMacOSButtonimplement interfaceButton.WindowsWindowandMacOSWindowimplement interfaceWindow.- Each family (Windows, macOS) has its own product, ensuring consistency.
Step 3: Define Abstract Factory Interface
// ui_factory.go
package main
type UIFactory interface {
CreateButton() Button
CreateWindow() Window
}Explain:
UIFactorydefine 2 methods:CreateButton()andCreateWindow().- Concrete Factory will implement this interface to create a product for each family.
Step 4: Create concrete factories
// windows_factory.go
package main
type WindowsFactory struct{}
func (f WindowsFactory) CreateButton() Button {
return WindowsButton{}
}
func (f WindowsFactory) CreateWindow() Window {
return WindowsWindow{}
}
// macos_factory.go
package main
type MacOSFactory struct{}
func (f MacOSFactory) CreateButton() Button {
return MacOSButton{}
}
func (f MacOSFactory) CreateWindow() Window {
return MacOSWindow{}
}Explain:
WindowsFactorycreateWindowsButtonandWindowsWindow.MacOSFactorycreateMacOSButtonandMacOSWindow.- Each factory produces all products in its family.
Step 5: Uses Abstract Factory
// main.go
package main
import "fmt"
func createUI(factory UIFactory) {
button := factory.CreateButton()
window := factory.CreateWindow()
fmt.Println(button.Render())
fmt.Println(window.Draw())
}
func main() {
// UI cho Windows
windowsFactory := WindowsFactory{}
createUI(windowsFactory)
// UI cho macOS
macOSFactory := MacOSFactory{}
createUI(macOSFactory)
}Explain:
- Client code (
createUI) just works withUIFactory, unknown to a specific class. maincan switch betweenWindowsFactoryandMacOSFactory.
Output run
Rendering a Windows button
Drawing a Windows window
Rendering a macOS button
Drawing a macOS windowExplain
- When use
WindowsFactory→ generates the entire Windows UI. - When use
MacOSFactory→ generated the entire macOS UI. - The client code remains unchanged, but the product family can be easily replaced.
Conclude
Abstract Factory Pattern allows to creation of groups of related objects in a flexible and consistent way. In Go, this pattern is especially useful when building multi-variant or cross-platform systems.
By using Abstract Factory, the code becomes:
- Easy to extend (add a new family, just add a factory).
- Easy to maintain (client does not depend on specific implementation).
- Ensure consistency (all objects come from the same family).
Next post in series: Singleton Pattern:
Thanks for reading!