Abstract Factory Design Pattern in Swift


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 dinner. It could be a delicious fried meat, a refreshing salad, or a mouthwatering cake. Yes, we’re having cake for dinner in this scenario. This relatable example will help you understand how the Abstract Factory pattern can be practically applied.

As an Abstract product, we will use the MealProtocol.

protocol MealProtocol {
    
    var name: String { get set }
    
    func cook()
    
}

extension MealProtocol {
    
    func cook() {
        print("The \(name) is cooked")
    }
    
}

We will use Salad, Cake, and Meat as Concrete products.

struct Meat: MealProtocol {
    var name = "Beef"
}

struct Salad: MealProtocol {
    var name = "Caesar Salad"
}

struct Cake: MealProtocol {
    var name = "Chocolate Cake"
}

As an Abstract factory, we will use MealFactoryProtocol.

protocol MealFactoryProtocol {

    func makeMeal() -> MealProtocol
    
}

We will use a Meat factory, a Salad Factory, and a Cake factory as Concrete factories.

final class MeatFactory: MealFactoryProtocol {

    func makeMeal() -> MealProtocol {
        Meat()
    }
    
}

final class SaladFactory: MealFactoryProtocol {

    func makeMeal() -> MealProtocol {
        Salad()
    }
    
}

final class CakeFactory: MealFactoryProtocol {

    func makeMeal() -> MealProtocol {
        Cake()
    }
    
}

Our Client will take care of the rest and provide us with the dinner.

enum MealKind {
    case salad, meat, cake
}

final class DinnerClient {
    
    private let mealFactory: MealFactoryProtocol
    
    init(mealKind: MealKind) {
        self.mealFactory = switch mealKind {
        case .salad:
            SaladFactory()
        case .meat:
            MeatFactory()
        case .cake:
            CakeFactory()
        }
    }
    
    func serve() -> MealProtocol {
        let meal: MealProtocol = mealFactory.makeMeal()
        meal.cook()
        
        return meal
    }
    
}

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!