Mastering Swift's Development Blog

Follow Us On Twitter
  • Jon Hoffman

Abstract Factory Pattern: Protocol Oriented Design Pattern


The abstract factory pattern is a creational design pattern where a super factory method is provided to encapsulate a group of individual factories which create instances of related types. The abstract factory encapsulates the selection of which factory method to use to create the necessary type. You can read about the factory method pattern in our Factory Method Pattern: Protocol Oriented Design Pattern post


Problem We Are Trying To Solve

We have an application where there are two or more factory methods that create related types and the selection of which factory method to use is dependent on certain runtime requirements. Without this pattern we would have the logic on which factory to choose embedded within our code, which would create a code base that is hard to troubleshoot and even harder to update. This is a very similar problem as the factory method pattern however the abstract factory pattern selects which factory method to use while the factory method pattern choses the type to use.


Our Solution

The abstract factory pattern suggests that we create a method that will encapsulate the selection of the factory types. This will decouple the selection and creation logic from the code that uses it and encapsulates this logic in one location making it easier to manage.


The Example

In this example, we are expanding on the drawing application example that we used in the factory method pattern, which has several predefined shapes that the user can select from. In this expansion we will be adding shapes that have rounded corners, which will have their own factory method for creation.






As usual in our design pattern tutorials, we are going to be showing the minimal amount of code necessary to demonstrate this pattern because we want the focus of this post to be on how the abstract factory design pattern works and not on implementing the example.


Since we are expanding on the example from the factory method pattern we will start with the code from that post and will briefly describe it.

enum ShapeType {
    case square
    case circle
    case triangle
}

protocol Shape {
    func draw()
}

struct Square: Shape {
    func draw() {}
}
struct Triangle: Shape {
    func draw() {}
}
struct Circle: Shape {
    func draw() {}
}


struct StandardShapeFactory {
    func getShape(_ shapeType: ShapeType) -> Shape {
        switch shapeType {
        case .square:
            return Square()
        case .circle:
            return Circle()
        case .triangle:
            return Triangle()
        }
    }
}

In this code we start off by defining an enum that contains all the different shapes. We then define our Shapeprotocol that all shape types will need to adopt. Finally, we define our three shape types and the factory which will create instances of these types. For a full explanation on this code please see our Factory Method Patter: Protocol Oriented Design Pattern post. Note: We renamed the ShapeFactory type in the factory method pattern post to StandardShapeFactory.

Now was we continue to build our application we would like to give the user the option of creating the shapes with rounded corners. The first thing we do is to create three new shape types that will draw the shapes with rounded corners.

struct RoundedSquare: Shape {
    func draw() {}
}
struct RoundedTriangle: Shape {
    func draw() {}
}
struct RoundedCircle: Shape {
    func draw() {}
}

Now our instinct may be to simply add these types to the list of shapes in our Shape type enum and add them to the list of shapes in our StandardShapeFactory type however knowing that we will be expanding on our application in the future we will probably need other modifiers for our shapes. This could make the list of shapes very long and hard to manage. Instead, we are going to be using the abstract factory pattern to solve this problem. We will begin by defining a ShapeModifer enum like this.

enum ShapeModifer {
    case standard
    case rounded
}

We will use this enum to define the modifier(s) for our shape type and will add to this as we add new modifiers. We will now create a AstractShapeFactory protocol that all shape factory types, including the StandardShapeFactory type defined earlier, will have to adopt.

protocol AbstractShapeFactory {
    func getShape(_ shapeType: ShapeType) -> Shape
}

We will adopt this protocol with our StandardShapeFactory type by adding the AstractShapeFactoryprotocol to the types definition like this:

struct StandardShapeFactory: AbstractShapeFactory {
   ...
}

We can now create a RoundedShapeFactory type which will be very similar to the StandardShapeFactorytype except we will return instances of the rounded shape types. The code for the StandardShapeFactorytype will look like this

struct RoundedShapeFactory: AbstractShapeFactory {
    func getShape(_ shapeType: ShapeType) -> Shape {
        switch shapeType {
        case .square:
            return RoundedSquare()
        case .circle:
            return RoundedCircle()
        case .triangle:
            return RoundedTriangle()
        }
    }
}

Finally, create our abstract factory which we can use to create our shapes likes this

struct ShapeFactory {
    static func getFactory(shapeModifer: ShapeModifer) -> AbstractShapeFactory {
        switch shapeModifer {
        case .rounded:
            return RoundedShapeFactory()
        case .standard:
            return StandardShapeFactory()
        }
    }
}

The getFactory() method will take a ShapeModifer value and return the correct factory method type to build the shape the user wants. We can then create our shapes like this.

var roundCircle = ShapeFactory.getFactory(shapeModifer: .rounded).getShape(.circle)

Once again, just like our factory method pattern, we have a single line of code to create instances of the types we want.


If we are using the factory pattern to create instances of types and we need to add another set of related types, as we had to with our shapes in this example, you should consider using the abstract factory pattern. It is extremely easy, as we just saw, to add the abstract factory pattern to an existing factory method pattern. This will also enable us to very easily add other related types in the future.



masteringSwift.jpeg

Mastering Swift 5.3

The sixth edition of this bestselling book, updated to cover through version 5.3 of the Swift programming language

Amazon

Packt

pop.jpeg

Protocol Oriented Programming

Embrace the Protocol-Oriented design paradigm, for better code maintainability and increased performance, with Swift.

Amazon

Packt