Chapters

Hide chapters

Design Patterns by Tutorials

Third Edition · iOS 13 · Swift 5 · Xcode 11

11. Factory Pattern
Written by Jay Strawn

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

The factory pattern is a creational pattern that provides a way to make objects without exposing creation logic. It involves two types:

  1. The factory creates objects.
  2. The products are the objects that are created.

Technically, there are multiple “flavors” of this pattern, including simple factory, abstract factory and others. However, each of these share a common goal: to isolate object creation logic within its own construct.

In this chapter, you’ll be adding onto the previous chapter’s project, Coffee Quest, to learn about a simple factory. It creates objects of a common type or protocol, and the factory’s type itself is known and used by consumers directly.

When should you use it?

Use the factory pattern whenever you want to separate out product creation logic, instead of having consumers create products directly.

A factory is very useful when you have a group of related products, such as polymorphic subclasses or several objects that implement the same protocol. For example, you can use a factory to inspect a network response and turn it into a concrete model subtype.

A factory is also useful when you have a single product type, but it requires dependencies or information to be provided to create it. For example, you can use a factory to create a “job applicant response” email: The factory can generate email details depending on whether the candidate was accepted, rejected or needs to be interviewed.

Playground example

Open IntermediateDesignPattern.xcworkspace in the Starter directory, or continue from your own playground workspace from the last chapter, then open the Factory page. As mentioned above, you’ll create a factory to generate job applicant response emails. Add the following after Code Example:

import Foundation

public struct JobApplicant {
  public let name: String
  public let email: String
  public var status: Status
  
  public enum Status {
    case new
    case interview
    case hired
    case rejected
  }
}

public struct Email {
  public let subject: String
  public let messageBody: String
  public let recipientEmail: String
  public let senderEmail: String
}
// 1
public struct EmailFactory {
  
  // 2
  public let senderEmail: String
  
  // 3
  public func createEmail(to recipient: JobApplicant) -> Email {
    let subject: String
    let messageBody: String

    switch recipient.status {
    case .new:
      subject = "We Received Your Application"
      messageBody = 
        "Thanks for applying for a job here! " +
        "You should hear from us in 17-42 business days."

    case .interview:
      subject = "We Want to Interview You"
      messageBody = 
        "Thanks for your resume, \(recipient.name)! " +
        "Can you come in for an interview in 30 minutes?"

    case .hired:
      subject = "We Want to Hire You"
      messageBody = 
        "Congratulations, \(recipient.name)! " +
        "We liked your code, and you smelled nice. " +
        "We want to offer you a position! Cha-ching! $$$"

    case .rejected:
      subject = "Thanks for Your Application"
      messageBody = 
        "Thank you for applying, \(recipient.name)! " +
        "We have decided to move forward " +
        "with other candidates. " +
        "Please remember to wear pants next time!"
    }

    return Email(subject: subject,
                 messageBody: messageBody,
                 recipientEmail: recipient.email,
                 senderEmail: senderEmail)
  }
}
var jackson = JobApplicant(name: "Jackson Smith",
                           email: "jackson.smith@example.com",
                           status: .new)

let emailFactory = 
  EmailFactory(senderEmail: "RaysMinions@RaysCoffeeCo.com")

// New
print(emailFactory.createEmail(to: jackson), "\n")

// Interview
jackson.status = .interview
print(emailFactory.createEmail(to: jackson), "\n")

// Hired
jackson.status = .hired
print(emailFactory.createEmail(to: jackson), "\n")

What should you be careful about?

Not all polymorphic objects require a factory. If your objects are very simple, you can always put the creation logic directly in the consumer, such as a view controller itself.

Tutorial project

You’ll continue the Coffee Quest app from the previous chapter. If you skipped the previous chapter, or you want a fresh start, open Finder and navigate to where you downloaded the resources for this chapter. Then, open starter\CoffeeQuest\CoffeeQuest.xcworkspace (not .xcodeproj) in Xcode.

import UIKit
import MapKit
import YelpAPI

public class AnnotationFactory {
  
  public func createBusinessMapViewModel(
    for business: YLPBusiness) -> BusinessMapViewModel? {
    
    guard 
      let yelpCoordinate = business.location.coordinate else {
        return nil
    }

    let coordinate = 
      CLLocationCoordinate2D(
        latitude: yelpCoordinate.latitude,
        longitude: yelpCoordinate.longitude)

    let name = business.name
    let rating = business.rating
    let image: UIImage
    switch rating {
    case 3.0..<3.5:
      image = UIImage(named: "bad")!
    case 3.5..<4.0:
      image = UIImage(named: "meh")!
    case 4.0..<4.75:
      image = UIImage(named: "good")!
    case 4.75...5.0:
      image = UIImage(named: "great")!
    default:
      image = UIImage(named: "bad")!
    }
    return BusinessMapViewModel(coordinate: coordinate,
                                image: image,
                                name: name,
                                rating: rating)
  }
}
switch rating {
case 0.0..<3.0:
  image = UIImage(named: "terrible")!
case 3.0..<3.5:
  image = UIImage(named: "bad")!
case 3.5..<4.0:
  image = UIImage(named: "meh")!
case 4.0..<4.75:
  image = UIImage(named: "good")!
case 4.75...5.0:
  image = UIImage(named: "great")!
default:
  image = UIImage(named: "bad")!
}
public let annotationFactory = AnnotationFactory()
private func addAnnotations() {    
  for business in businesses {
    guard let viewModel = 
      annotationFactory.createBusinessMapViewModel(
        for: business) else {
          continue
    }
    mapView.addAnnotation(viewModel)
  }
}

Key points

You learned about the factory pattern in this chapter. Here are its key points:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now