Design Pattern Series - Abstract Factory Pattern (Golang)

Go Design Pattern Series May 27, 2025

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:

  • Button required method Render().
  • Window required method Draw().
  • 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:

  • WindowsButton and MacOSButton implement interface Button.
  • WindowsWindow and MacOSWindow implement interface Window.
  • 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:

  • UIFactory define 2 methods: CreateButton() and CreateWindow().
  • 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:

  • WindowsFactory create WindowsButton and WindowsWindow.
  • MacOSFactory create MacOSButton and MacOSWindow.
  • 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 with UIFactory, unknown to a specific class.
  • main can switch between WindowsFactory and MacOSFactory.

Output run

Rendering a Windows button
Drawing a Windows window
Rendering a macOS button
Drawing a macOS window

Explain

  • 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!

Tags