Abstract factory: Swift version

Ario Liyan
8 min readFeb 11, 2023

--

This article takes a deep dive into the Factory Pattern, discussing the Factory Method, Abstract Factory, and Simple Factory. It was written after reading three books(Design patterns: elements of reusable object-oriented software, Head First Design Pattern, and Dive Into Design Patterns), watching a few YouTube videos, and reading various articles. Written with Swift in mind, it serves as a comprehensive reference. In Part One, we discussed the Factory Method and Simple Factory patterns. In this part, we will explore the Abstract Factory pattern.

It is advised to read the abstract factory method pattern first.

Table of contents

  • What is an abstract factory pattern?
  • Why do we need the abstract factory pattern?
  • Where and when to use the factory pattern?
  • UML
  • Let’s see some code
  • Conclusion

What is an abstract factory pattern?

The abstract factory pattern is very similar to the factory method pattern. In some ways, an abstract factory is a collection of factory methods; in other words, an abstract factory utilizes multiple factory methods.

The abstract factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

The abstract factory enables a system to be independent of how its objects are created, composed, and represented. This makes it possible to interchange components without changing the application code.

So if we contrast this definition to the factory method pattern, we come to understand that the only difference with the factory method pattern is that the factory method pattern only creates one object but the abstract factory constructs multiple objects.

Why do we need the abstract factory pattern?

Imagine that we’re creating a Style simulation app to help people put clothing on a mannequin. Your code consists of classes that represent:

  1. A family of related classes (products in factory pattern) say Jacket + Footwear + Pants.
  2. Several variants of this family. For example, products Jacket + Footwear + Pants are available in these variants: Sport set, Winter set, Leather Set.
An example of the abstract factory.

Creating individual clothing items that match other items of the same family is essential to ensure customer satisfaction. Without this, customers may become frustrated when they receive mismatched clothing.

Also, you don’t want to change existing code when adding new products or families of products to the program. Brand vendors update their catalogs very often, and you wouldn’t want to change the core code each time it happens.

It is then that you know that you need an abstract factory.

Advantages of Using Abstract Factory Pattern

The advantages of using factories include:

  • You can be sure that the products you’re getting from a factory are compatible with each other.
  • You avoid tight coupling between concrete products and client code.
  • Single Responsibility Principle. You can extract the product creation code into one place, making the code easier to support.
  • Open/Closed Principle. You can introduce new variants of products without breaking existing client code.

As for the disadvantages:

The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern.

Where and when to use the factory pattern?

The Abstract Factory pattern is useful when your code needs to interact with multiple families of related products, but you don’t want it to be dependent on the concrete classes of those products. This is because the concrete classes may be unknown beforehand or you may want to allow for future extensibility.

The Abstract Factory offers an interface for creating objects from each class of the product family. This ensures that the objects created by your code are compatible with the products already created by your app, avoiding any mismatches.

Consider implementing the Abstract Factory when you have a class with a set of Factory Methods that blur its primary responsibility.

In a well-designed program, each class should only be responsible for one task. When a class is dealing with multiple product types, it may be beneficial to extract its factory methods into a separate factory class, or even a full Abstract Factory implementation.

UML

Abstract factory UML Taken from Gangs of four book.
  1. Abstract Products declare interfaces for a set of distinct but related products which make up a product family.
  2. Concrete Products are various implementations of abstract products, grouped by variants. Each abstract product must be implemented in all given variants.
  3. The Abstract Factory interface declares a set of methods for creating each of the abstract products.
  4. Concrete Factories implement the creation methods of the abstract factory. Each concrete factory corresponds to a specific variant of products and creates only those product variants.
  5. Although concrete factories instantiate concrete products, signatures of their creation methods must return corresponding abstract products. This way the client code that uses a factory doesn’t get coupled to the specific variant of the product it gets from a factory. The Client can work with any concrete factory/product variant, as long as it communicates with their objects via abstract interfaces.

Let’s see some code

import Foundation

//Factory protocol - Part 1
protocol WinterSetFactory {
func createWinterFootwear(name: String, brand: Brand) -> Footwear
func createWinterJacket(name: String, brand: Brand) -> Jacket
func createWinterPants(name: String, brand: Brand) -> Pants
}

protocol SportSetFactory {
func createSportFootwear(name: String, brand: Brand)-> Footwear
func createSportJacket(name: String, brand: Brand) -> Jacket
func createSportPants(name: String, brand: Brand) -> Pants
}

protocol LeatherSetFactory {
func createLeatherFootwear(name: String, brand: Brand) -> Footwear
func createLeatherJacket(name: String, brand: Brand) -> Jacket
func createLeatherPants(name: String, brand: Brand)-> Pants
}


enum Brand {
case Adidas
case Nike
case Versache
}

//Product protocol - Part 2
protocol Jacket {
var name: String {get set}
var brand: Brand {get set }
}

protocol Pants {
var name: String {get set}
var brand: Brand {get set }

}

protocol Footwear {
var name: String {get set}
var brand: Brand {get set }

}


//Concrete Products Part 3
//Jacket Products
class WinterJacket: Jacket {
var name: String
var brand: Brand

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

class SportJacket: Jacket {
var name: String
var brand: Brand

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

class LeatherJacket: Jacket {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

//Pants Products
class WinterPants: Pants {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

class SportPants: Pants {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

class LeatherPants: Pants {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

//Footwear Products
class WinterFootwear: Footwear {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

class SportFootwear: Footwear {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}

class LeatherFootwear: Footwear {
var brand: Brand
var name: String

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}


//Concrete Factories - Part 4
class WinterSetClothFactory: WinterSetFactory {
func createWinterFootwear(name: String, brand: Brand) -> Footwear {
WinterFootwear(name: name, brand: brand)
}

func createWinterJacket(name: String, brand: Brand) -> Jacket {
WinterJacket(name: name, brand: brand)
}

func createWinterPants(name: String, brand: Brand) -> Pants {
WinterPants(name: name, brand: brand)
}
}

class SportSetClothFactory: SportSetFactory {
func createSportFootwear(name: String, brand: Brand) -> Footwear {
SportFootwear(name: name, brand: brand)
}

func createSportJacket(name: String, brand: Brand) -> Jacket {
SportJacket(name: name, brand: brand)
}

func createSportPants(name: String, brand: Brand) -> Pants {
SportPants(name: name, brand: brand)
}
}

class LeatherSetClothFactory: LeatherSetFactory {
func createLeatherFootwear(name: String, brand: Brand) -> Footwear {
LeatherFootwear(name: name, brand: brand)
}

func createLeatherJacket(name: String, brand: Brand) -> Jacket {
LeatherJacket(name: name, brand: brand)
}

func createLeatherPants(name: String, brand: Brand) -> Pants {
LeatherPants(name: name, brand: brand)
}
}

Factory protocol — Part 1

In the first part, we have the factory protocols, each factory is responsible to create related objects for the relevant ClothSet, for example, the given factory below is responsible to create all Clothing relate to the winter set.

protocol WinterSetFactory {
func createWinterFootwear(name: String, brand: Brand) -> Footwear
func createWinterJacket(name: String, brand: Brand) -> Jacket
func createWinterPants(name: String, brand: Brand) -> Pants
}

Product protocol — Part 2

In the second part, we have protocols for our products based on their types…

The given protocol is for the Jacket and each Jacket type should conform to this protocol. It is the Jacket type that the factory returns for the client not the concrete type of the Jacket….

//Product protocol - Part 2
protocol Jacket {
var name: String {get set}
var brand: Brand {get set }
}

enum Brand {
case Adidas
case Nike
case Versache
}

Concrete Products — Part 3

Concrete products that each conform to their relative protocol, concrete products are created based on the needs of the business and can be added without a need to change the current code and the system logic.

//Concrete Products Part 3
//Jacket Products
class WinterJacket: Jacket {
var name: String
var brand: Brand

init(name: String, brand: Brand) {
self.name = name
self.brand = brand
}
}
...

Concrete Factories — Part 4

Each concrete factory should conform to its relative protocol, and within the factory should be functions implemented to return related objects in our case the factories are responsible to create clothing sets.

//Concrete Factories - Part 4 
class WinterSetClothFactory: WinterSetFactory {
func createWinterFootwear(name: String, brand: Brand) -> Footwear {
WinterFootwear(name: name, brand: brand)
}

func createWinterJacket(name: String, brand: Brand) -> Jacket {
WinterJacket(name: name, brand: brand)
}

func createWinterPants(name: String, brand: Brand) -> Pants {
WinterPants(name: name, brand: brand)
}
}

Conclusions

Abstract factories are in short just a set of factory methods to create a set of related objects, as any other pattern use them in their place so your code becomes more flexible and robust.

--

--

Ario Liyan

As an iOS developer with a passion for programming concepts. I love sharing my latest discoveries with others and sparking conversations about technology.