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

From REST API to CoreData in One Step

See how to convert fetched data from a REST API to entities and persist them to CoreData, the built-in local database of Apple.

• 3 min read

REST APICoreData

Read and Write Data in a Sandboxed App (Part 1)

Discover how to get read and write permission to user's Mac folders and files in an app with Sandbox enabled.

• 4 min read

MacOSFile system