Menu
Coddy logo textTech

Factory Pattern

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 87 of 107.

The Factory pattern delegates object creation to a separate function or method, allowing you to create instances without exposing the creation logic. Unlike Singleton which controls instance count, Factory focuses on flexible object creation based on input parameters.

In Go, a factory is typically a function that returns an interface type, letting you create different concrete types through a unified API:

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
type SMSNotifier struct{}

func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

func NewNotifier(notifierType string) Notifier {
    switch notifierType {
    case "email":
        return EmailNotifier{}
    case "sms":
        return SMSNotifier{}
    default:
        return EmailNotifier{}
    }
}

The factory function NewNotifier encapsulates the decision of which concrete type to create. Client code works only with the Notifier interface, remaining unaware of the specific implementation:

notifier := NewNotifier("sms")
result := notifier.Send("Hello!")  // "SMS: Hello!"

This pattern shines when you need to create objects based on runtime conditions, want to centralize complex initialization logic, or need to easily add new types without modifying client code. Simply add a new struct implementing the interface and update the factory function.

challenge icon

Challenge

Easy

Let's build a document converter system using the Factory pattern! You'll create a factory that produces different document converters based on the target format, allowing client code to work with any converter through a unified interface.

You'll organize your code across two files:

  • converter.go: Define your converter interface and concrete implementations.

    Create a Converter interface with a method Convert(content string) string that takes document content and returns the converted output.

    Implement three converter types:

    • PDFConverter — its Convert method returns PDF: [content]
    • HTMLConverter — its Convert method returns HTML: <p>[content]</p>
    • MarkdownConverter — its Convert method returns MD: # [content]

    Create a factory function NewConverter(format string) Converter that returns the appropriate converter based on the format string (pdf, html, or markdown). For any unrecognized format, return a PDFConverter as the default.

  • main.go: Use your factory to create converters and process documents.

    Read a format type and document content. Use the factory to create the appropriate converter, then convert the content and print the result.

The following inputs will be provided:

  • Line 1: Format type (pdf, html, markdown, or something else)
  • Line 2: Document content to convert

For example, given:

html
Welcome to Go Programming

Your output should be:

HTML: <p>Welcome to Go Programming</p>

And given:

markdown
Chapter One

Your output should be:

MD: # Chapter One

And given:

pdf
Annual Report 2024

Your output should be:

PDF: Annual Report 2024

And given:

docx
Meeting Notes

Your output should be:

PDF: Meeting Notes

Notice how the main code doesn't need to know about specific converter types—it just asks the factory for a converter and uses the interface. This makes it easy to add new formats later without changing the client code!

Cheat sheet

The Factory pattern delegates object creation to a separate function, allowing you to create instances without exposing the creation logic. It focuses on flexible object creation based on input parameters.

A factory function returns an interface type, enabling creation of different concrete types through a unified API:

type Notifier interface {
    Send(message string) string
}

type EmailNotifier struct{}
type SMSNotifier struct{}

func (e EmailNotifier) Send(message string) string {
    return "Email: " + message
}

func (s SMSNotifier) Send(message string) string {
    return "SMS: " + message
}

func NewNotifier(notifierType string) Notifier {
    switch notifierType {
    case "email":
        return EmailNotifier{}
    case "sms":
        return SMSNotifier{}
    default:
        return EmailNotifier{}
    }
}

Client code works only with the interface, remaining unaware of specific implementations:

notifier := NewNotifier("sms")
result := notifier.Send("Hello!")  // "SMS: Hello!"

This pattern is useful when you need to create objects based on runtime conditions, want to centralize complex initialization logic, or need to add new types without modifying client code.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Read format type
	scanner.Scan()
	format := scanner.Text()
	
	// Read document content
	scanner.Scan()
	content := scanner.Text()
	
	// TODO: Use the factory to create the appropriate converter
	// TODO: Convert the content and print the result
	
	fmt.Println(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