Menu
Coddy logo textTech

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 icon

Challenge

Easy

Let'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:

    • Chair with a method SitOn() string
    • Table with a method PlaceItems() string

    Implement two families of furniture:

    • Modern family: ModernChair returns Sitting on a sleek modern chair and ModernTable returns Placing items on a glass modern table
    • Vintage family: VintageChair returns Sitting on an ornate vintage chair and VintageTable returns Placing items on a wooden vintage table
  • factory.go: Define your abstract factory interface and concrete factories.

    Create a FurnitureFactory interface with two methods:

    • CreateChair() Chair
    • CreateTable() Table

    Implement two concrete factories:

    • ModernFactory that produces ModernChair and ModernTable
    • VintageFactory that produces VintageChair and VintageTable

    Also create a function GetFactory(style string) FurnitureFactory that returns the appropriate factory based on the style (modern or vintage). Default to ModernFactory for unrecognized styles.

  • main.go: Use your factories to furnish rooms.

    Create a function FurnishRoom(factory FurnitureFactory) string that 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:

modern

Your output should be:

Sitting on a sleek modern chair
Placing items on a glass modern table

And given:

vintage

Your output should be:

Sitting on an ornate vintage chair
Placing items on a wooden vintage table

And given:

rustic

Your output should be:

Sitting on a sleek modern chair
Placing items on a glass modern table

Notice 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
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming