Intermediate Design Patterns in Swift
Design patterns are incredibly useful for making code maintainable and readable. Learn design patterns in Swift with this hands on tutorial. By .
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Intermediate Design Patterns in Swift
50 mins
- Getting Started
- Understanding the Game
- Why Use Design Patterns?
- Design Pattern: Abstract Factory
- Design Pattern: Servant
- Leveraging Abstract Factory for Gameplay Versatility
- Design Pattern: Builder
- Design Pattern: Dependency Injection
- Design Pattern: Strategy
- Design Patterns: Chain of Responsibility, Command and Iterator
- Where To Go From Here?
Design Pattern: Builder
Now it’s time to examine a third design pattern: Builder.
Suppose you want to vary the appearance of your ShapeView instances — whether they should show fill and outline colors and what colors to use. The Builder design pattern makes such object configuration easier and more flexible.
One approach to solve this configuration problem would be to add a variety of constructors, either class convenience methods like CircleShapeView.redFilledCircleWithBlueOutline() or initializers with a variety of arguments and default values.
Unfortunately, it’s not a scalable technique as you’d need to write a new method or initializer for every combination.
Builder solves this problem rather elegantly because it creates a class with a single purpose — configure an already initialized object. If you set up your builder to build red circles and then later blue circles, it’ll do so without need to alter CircleShapeView.
Create a new file ShapeViewBuilder.swift and replace its contents with the following code:
import Foundation
import UIKit
class ShapeViewBuilder {
// 1
var showFill = true
var fillColor = UIColor.orangeColor()
// 2
var showOutline = true
var outlineColor = UIColor.grayColor()
// 3
init(shapeViewFactory: ShapeViewFactory) {
self.shapeViewFactory = shapeViewFactory
}
// 4
func buildShapeViewsForShapes(shapes: (Shape, Shape)) -> (ShapeView, ShapeView) {
let shapeViews = shapeViewFactory.makeShapeViewsForShapes(shapes)
configureShapeView(shapeViews.0)
configureShapeView(shapeViews.1)
return shapeViews
}
// 5
private func configureShapeView(shapeView: ShapeView) {
shapeView.showFill = showFill
shapeView.fillColor = fillColor
shapeView.showOutline = showOutline
shapeView.outlineColor = outlineColor
}
private var shapeViewFactory: ShapeViewFactory
}
Here’s how your new ShapeViewBuilder works:
- Store configuration to set
ShapeViewfill properties. - Store configuration to set
ShapeViewoutline properties. - Initialize the builder to hold a
ShapeViewFactoryto construct the views. This means the builder doesn’t need to know if it’s buildingSquareShapeVieworCircleShapeViewor even some other kind of shape view. - This is the public API; it creates and initializes a pair of
ShapeViewwhen there’s a pair ofShape. - Do the actual configuration of a
ShapeViewbased on the builder’s stored configuration.
Deploying your spiffy new ShapeViewBuilder is as easy as opening GameViewController.swift and adding the following code to the bottom of the class, just before the closing curly brace:
private var shapeViewBuilder: ShapeViewBuilder!
Now, populate your new property by adding the following code to viewDidLoad just above the line that invokes beginNextTurn:
shapeViewBuilder = ShapeViewBuilder(shapeViewFactory: shapeViewFactory)
shapeViewBuilder.fillColor = UIColor.brownColor()
shapeViewBuilder.outlineColor = UIColor.orangeColor()
Finally replace the line that creates shapeViews in beginNextTurn with the following:
let shapeViews = shapeViewBuilder.buildShapeViewsForShapes(shapes)
Build and run, and you should see something like this:
Notice how your circles are now a pleasant brown with orange outlines — I know you must be amazed by the stunning design here, but please don’t try to hire me to be your interior decorator. ;]
Now to reinforce the power of the Builder pattern. With GameViewController.swift still open, change your viewDidLoad to use square factories:
shapeViewFactory = SquareShapeViewFactory(size: gameView.sizeAvailableForShapes())
shapeFactory = SquareShapeFactory(minProportion: 0.3, maxProportion: 0.8)
Build and run, and you should see this.
Notice how the Builder pattern made it easy to apply a new color scheme to squares as well as to circles. Without it, you’d need color configuration code in both CircleShapeViewFactory and SquareShapeViewFactory.
Furthermore, changing to another color scheme would involve widespread code changes. By restricting ShapeView color configuration to a single ShapeViewBuilder, you also isolate color changes to a single class.
Design Pattern: Dependency Injection
Every time you tap a shape, you’re taking a turn in your game, and each turn can be a match or not a match.
Wouldn’t it be helpful if your game could track all the turns, stats and award point bonuses for hot streaks?
Create a new file called Turn.swift, and replace its contents with the following code:
import Foundation
class Turn {
// 1
let shapes: [Shape]
var matched: Bool?
init(shapes: [Shape]) {
self.shapes = shapes
}
// 2
func turnCompletedWithTappedShape(tappedShape: Shape) {
var maxArea = shapes.reduce(0) { $0 > $1.area ? $0 : $1.area }
matched = tappedShape.area >= maxArea
}
}
Your new Turn class does the following:
- Store the shapes that the player saw during the turn, and also whether the turn was a match or not.
- Records the completion of a turn after a player taps a shape.
To control the sequence of turns your players play, create a new file named TurnController.swift, and replace its contents with the following code:
import Foundation
class TurnController {
// 1
var currentTurn: Turn?
var pastTurns: [Turn] = [Turn]()
// 2
init(shapeFactory: ShapeFactory, shapeViewBuilder: ShapeViewBuilder) {
self.shapeFactory = shapeFactory
self.shapeViewBuilder = shapeViewBuilder
}
// 3
func beginNewTurn() -> (ShapeView, ShapeView) {
let shapes = shapeFactory.createShapes()
let shapeViews = shapeViewBuilder.buildShapeViewsForShapes(shapes)
currentTurn = Turn(shapes: [shapeViews.0.shape, shapeViews.1.shape])
return shapeViews
}
// 4
func endTurnWithTappedShape(tappedShape: Shape) -> Int {
currentTurn!.turnCompletedWithTappedShape(tappedShape)
pastTurns.append(currentTurn!)
var scoreIncrement = currentTurn!.matched! ? 1 : -1
return scoreIncrement
}
private let shapeFactory: ShapeFactory
private var shapeViewBuilder: ShapeViewBuilder
}
Your TurnController works as follows:
- Stores both the current turn and past turns.
- Accepts a
ShapeFactoryandShapeViewBuilder. - Uses this factory and builder to create shapes and views for each new turn and records the current turn.
- Records the end of a turn after the player taps a shape, and returns the computed score based on whether the turn was a match or not.
Now open GameViewController.swift, and add the following code at the bottom, just above the closing curly brace:
private var turnController: TurnController!
Scroll up to viewDidLoad, and just before the line invoking beginNewTurn, insert the following code:
turnController = TurnController(shapeFactory: shapeFactory, shapeViewBuilder: shapeViewBuilder)
Replace beginNextTurn with the following:
private func beginNextTurn() {
// 1
let shapeViews = turnController.beginNewTurn()
shapeViews.0.tapHandler = {
tappedView in
// 2
self.gameView.score += self.turnController.endTurnWithTappedShape(tappedView.shape)
self.beginNextTurn()
}
// 3
shapeViews.1.tapHandler = shapeViews.0.tapHandler
gameView.addShapeViews(shapeViews)
}
Your new code works as follows:
- Asks the
TurnControllerto begin a new turn and return a tuple ofShapeViewto use for the turn. - Informs the turn controller that the turn is over when the player taps a
ShapeView, and then it increments the score. Notice howTurnControllerabstracts score calculation away, further simplifyingGameViewController. - Since you removed explicit references to specific shapes, the second shape view can share the same
tapHandlerclosure as the first shape view.
An example of the Dependency Injection design pattern is that it passes in its dependencies to the TurnController initializer. The initializer parameters essentially inject the shape and shape view factory dependencies.
Since TurnController makes no assumptions about which type of factories to use, you’re free to swap in different factories.
Not only does this make your game more flexible, but it makes automated testing easier since it allows you to pass in special TestShapeFactory and TestShapeViewFactory classes if you desire. These could be special stubs or mocks that would make testing easier, more reliable or faster.
Build and run and check that it looks like this:
There are no visual differences, but TurnController has opened up your code so it can use more sophisticated turn strategies: calculating scores based on streaks of turns, alternating shape type between turns, or even adjusting the difficulty of play based on the player’s performance.


