Abstract Factory Design Pattern in Swift


Greetings, traveler!

In the previous article, we started to learn about Creational design patterns and discussed the Factory Method. This time, we will look at the Abstract Factory pattern.

An Abstract Factory can be helpful in situations where we want to hide the internal details of an object creation behind a public API. This pattern can also be beneficial when creating an object is a complex process that needs to be repeated many times throughout an application.

Unlike Java, C++, and C#, Swift doesn’t have an abstract class. But fear not — we can harness the power of Swift Protocols to implement a similar design pattern effectively.

The Abstract Factory design pattern has five main components:

  1. Abstract product
  2. Concrete product
  3. Abstract factory
  4. Concrete factory
  5. Client

Example of usage

Let’s bring the Abstract Factory pattern to life with a relatable example.
Imagine you’re preparing a full dinner — a main dish, a dessert, and a drink.
Each cuisine (Italian, Japanese, etc.) provides its own set of dishes that work well together.
That’s exactly what the Abstract Factory pattern represents: creating consistent families of related products.

Abstract Products

We start by defining our abstract product protocols:

protocol MainDishProtocol {
    func cook()
}

protocol DessertProtocol {
    func serve()
}

protocol DrinkProtocol {
    func pour()
}

Concrete Products

Each product family will have its own concrete implementations:

struct Pasta: MainDishProtocol {
    func cook() { print("🍝 Cooking pasta") }
}

struct Tiramisu: DessertProtocol {
    func serve() { print("🍰 Serving tiramisu") }
}

struct Wine: DrinkProtocol {
    func pour() { print("🍷 Pouring wine") }
}

struct Sushi: MainDishProtocol {
    func cook() { print("🍣 Cooking sushi") }
}

struct Mochi: DessertProtocol {
    func serve() { print("🍡 Serving mochi") }
}

struct Sake: DrinkProtocol {
    func pour() { print("🍶 Pouring sake") }
}

Abstract Factory

Now we define the interface for creating a whole family of products:

protocol DinnerFactoryProtocol {
    func makeMainDish() -> MainDishProtocol
    func makeDessert() -> DessertProtocol
    func makeDrink() -> DrinkProtocol
}

Concrete Factories

Each concrete factory will produce a consistent set of related products:

struct ItalianDinnerFactory: DinnerFactoryProtocol {
    func makeMainDish() -> MainDishProtocol { Pasta() }
    func makeDessert() -> DessertProtocol { Tiramisu() }
    func makeDrink() -> DrinkProtocol { Wine() }
}

struct JapaneseDinnerFactory: DinnerFactoryProtocol {
    func makeMainDish() -> MainDishProtocol { Sushi() }
    func makeDessert() -> DessertProtocol { Mochi() }
    func makeDrink() -> DrinkProtocol { Sake() }
}

Client

Finally, the client depends only on the abstract factory, not on any specific types:

final class DinnerClient {
    private let factory: DinnerFactoryProtocol

    init(factory: DinnerFactoryProtocol) {
        self.factory = factory
    }

    func serveDinner() {
        let main = factory.makeMainDish()
        let dessert = factory.makeDessert()
        let drink = factory.makeDrink()

        main.cook()
        dessert.serve()
        drink.pour()
    }
}

Usage

let italianDinner = DinnerClient(factory: ItalianDinnerFactory())
italianDinner.serveDinner()

let japaneseDinner = DinnerClient(factory: JapaneseDinnerFactory())
japaneseDinner.serveDinner()

Conclusion

The job is done, and I hope you have enjoyed this article. The following article will discuss how and when to use the Builder Pattern. See you soon!