Menu
Coddy logo textTech

Factory Pattern

Part of the Object Oriented Programming section of Coddy's Swift journey. Lesson 43 of 57.

The factory pattern puts object creation in one place. Callers ask for what they need, often by name or type, and receive a ready object, without knowing which concrete type was built:

protocol Shape { func area() -> Double }
struct Square: Shape { let side: Double; func area() -> Double { side * side } }
struct Circle: Shape { let r: Double; func area() -> Double { 3 * r * r } }

enum ShapeFactory {
    static func make(_ kind: String, size: Double) -> (any Shape)? {
        switch kind {
        case "square": return Square(side: size)
        case "circle": return Circle(r: size)
        default: return nil
        }
    }
}

print(ShapeFactory.make("square", size: 3)!.area())
print(ShapeFactory.make("hexagon", size: 3) == nil)

Output:

9.0
true

The caller depends only on the protocol. Adding a new shape means changing the factory, not every place that creates shapes:

protocol Notifier { func send(_ m: String) -> String }
struct Email: Notifier { func send(_ m: String) -> String { "email: \(m)" } }
struct SMS: Notifier { func send(_ m: String) -> String { "sms: \(m)" } }

func makeNotifier(for user: String) -> any Notifier {
    user.hasSuffix("@mail.com") ? Email() : SMS()
}

print(makeNotifier(for: "ada@mail.com").send("hi"))
print(makeNotifier(for: "555-0100").send("hi"))

Output:

email: hi
sms: hi

Static factory methods on the type itself are a lighter form of the pattern. They give names to common ways of building a value:

struct Color {
    let r: Int, g: Int, b: Int
    static func gray(_ v: Int) -> Color { Color(r: v, g: v, b: v) }
    static let white = Color(r: 255, g: 255, b: 255)
}

print(Color.gray(100).g)
print(Color.white.b)

Output:

100
255

An enum without cases, such as ShapeFactory, is a common way to group factory functions: it cannot be instantiated, so it works purely as a namespace.

enum ShapeFactory {        // no cases: cannot be created
    static func make(_ kind: String, size: Double) -> (any Shape)? { ... }
}
challenge icon

Challenge

Easy

Write a factory for pricing plans. The protocol Plan has name and price(forUsers:) -> Int. Implement Free (0, and only up to 3 users: above that the price is -1 meaning not allowed), Team (10 per user) and Enterprise (500 flat plus 5 per user above 100). Complete PlanFactory.make(_:), which returns the plan for free, team or enterprise and nil otherwise.

The supplied code reads lines plan,users and prints team for 12 users: 120, free does not allow 5 users or unknown plan: gold.

Your code goes in Plan.swift, Free.swift, Team.swift, Enterprise.swift and PlanFactory.swift. main.swift holds the supplied input/output code and cannot be edited.

Try it yourself

// Supplied input/output code: keep it as it is
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    let p = line.split(separator: ",").map(String.init)
    let users = Int(p[1])!
    guard let plan = PlanFactory.make(p[0]) else {
        print("unknown plan: \(p[0])")
        continue
    }
    let price = plan.price(forUsers: users)
    print(price < 0 ? "\(plan.name) does not allow \(users) users" : "\(plan.name) for \(users) users: \(price)")
}
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

Practice on your own: Swift playground