Abstract Factory Pattern in Swift

Learn how the abstract factory pattern allows you to improve you codebase making it more readable and maintainable.

Francesco Leoni

• 3 min read

Abstract Factory is a pattern that allow you to create families of object that conform to a common protocol.

This patterns has three main components: the factory, the product and a client.

Factory:

  1. Factory protocol: Declares an interface that allows to create abstract objects.
  2. Concrete factory (class or struct): Implements the operations to create abstract objects.

Product:

  1. Product protocol: Declares an interface that defines a product object.
  2. Concrete product (class or struct): Implements the AbstractProduct interface.

Client:

  1. Client (class or struct): Uses only interfaces declared by Factory protocol and Product protocol.

Implementation

This example illustrates the structure of the Abstract Factory design pattern.

Products

We create a Product protocol that defines a single product. And then we add the concrete classes that implements the Product protocol.

// Products
protocol Car {
 
    func drive()
    func canBeDriven(by driver: Driver) -> Bool
}
 
class ToyotaCar: Car {
 
    func drive() {
        print("Start drive Toyota")
    }
 
    func canBeDriven(by driver: Driver) -> Bool {
        if driver is ProfessionalDriver {
            return true
        } else {
            return false
        }
    }
}
 
class LamborghiniCar: Car {
 
    func drive() {
        print("Start drive Lamborghini")
    }
 
    func canBeDriven(by driver: Driver) -> Bool {
        if driver is ProfessionalDriver {
            return true
        } else {
            return false
        }
    }
}
 
protocol Driver {
 
    var name: String { get set }
    var surname: String { get set }
 
    func whoAmI()
}
 
class ProfessionalDriver: Driver {
 
    var name: String
    var surname: String
 
    init(name: String, surname: String) {
        self.name = name
        self.surname = surname
    }
 
    func whoAmI() {
        print("I am a professional driver.")
    }
}
 
class NewbieDriver: Driver {
 
    var name: String
    var surname: String
 
    init(name: String, surname: String) {
        self.name = name
        self.surname = surname
    }
 
    func whoAmI() {
        print("I am a newbie driver.")
    }
}

Factories

We create a Factory protocol that defines an interface for creating all distinct products. And then we add the concrete classes that implements the Factory protocol.

// Factories
protocol Factory {
 
    func hireDriver() -> Driver
    func buildCar() -> Car
}
 
class ToyotaFactory: Factory {
 
    func hireDriver() -> Driver {
        ProfessionalDriver(name: "Fernando", surname: "Alonso")
    }
 
    func buildCar() -> Car {
        ToyotaCar()
    }
}
 
class LamborghiniFactory: Factory {
 
    func hireDriver() -> Driver {
        NewbieDriver(name: "Jhon", surname: "Smith")
    }
 
    func buildCar() -> Car {
        LamborghiniCar()
    }
}

Client

Here we create the client that calls the creation methods of a factory object instead of creating products directly with a constructor call.

Tip

In the init we used Dependency Injection that allows us to inject a factory in the client.

class Client {
 
    let factory: Factory
 
    var driverName: String {
        let driver = self.hireDriver()
        return "\(driver.name) \(driver.surname)"
    }
 
    // Inits
 
    init(factory: Factory) {
        self.factory = factory
    }
 
    // Methods
 
    func buildCar() -> Car {
        self.factory.buildCar()
    }
 
    func hireDriver() -> Driver {
        self.factory.hireDriver()
    }
 
    func canBeDriven() -> Bool {
        self.buildCar().canBeDriven(by: hireDriver())
    }
}

Usage

Now we can instanciate the client providing a factory.

Conclusion

PROS

  • Isolation of concrete classes. It helps to control the classes of objects created. Because a factory encapsulates the responsibility and process of creating objects and isolates clients from concrete classes.
  • Objects of the same family can be easily interchanged. Each product of the same class can be swapped since they all conform to the same protocol.
  • Promotes consistency between objects of the same family. Each object of the same family has equal methods and property defined in the implemented protocol.

CONS

  • Supporting new product types. Since the Factory protocol defines all the various types of products that can be instantiated, adding a family means changing the factory interface. This change affects the concrete factories and all the subclasses, making the operation laborious.

If you have any question about this article, feel free to email me or tweet me @franceleonidev and share your opinion.

Thank you for reading and see you in the next article!

Share this article

Related articles


Understanding Dependency Injection in Swift

Learn the basics of dependency injection with Swift and Xcode. It will allows you to inject dependencies making classes flexible.

• 3 min read

Patterns

CloudKit With CoreData Not Working in Production

Discover why sometimes the sync between CoreData and CloudKit do not work in production but it does while developing.

• 1 min read

Q&AErrors

Tutorial: How to Create a Timed Paging Carousel Like Instagram

Learn how to create a page controller that automatically changes page with the new UIPageControlTimerProgress component.

• 4 min read

SwiftUIKit