Factory Method, Design Patterns Series -Part 3
Hey iOS developers! welcome back to series of design patterns Let’s talk about object creation the Factory pattern
What’s the Factory Method?
The Factory Method is a creational design pattern where a superclass defines an interface for creating objects, but lets subclasses decide which class to instantiate. Think of it as a stamp machine — you press a button, and it delivers the exact object you need.
Key Players:
- Creator Protocol/Class: Declares the factory method.
- Concrete Creators: Override the factory method to produce specific objects.
- Product Protocol: The type of object being created.
SwiftUI’s Factory Method Magic
Ever wondered how Button
or List
in SwiftUI handles different initializers so seamlessly? It’s all thanks to @ViewBuilder
, which acts as a factory for constructing view hierarchies!
Example: Custom Alert Factory
protocol AlertFactory {
func makeAlert() -> Alert
}
struct ErrorAlert: AlertFactory {
func makeAlert() -> Alert {
Alert(title: Text("Oops!"), message: Text("Something went wrong."))
}
}
struct SuccessAlert: AlertFactory {
func makeAlert() -> Alert {
Alert(title: Text("Success!"))
}
}
// Usage in SwiftUI:
struct ContentView: View {
let alertFactory: AlertFactory
var body: some View {
Button("Show Alert") { ... }
.alert(alertFactory.makeAlert())
}
}
This approach decouples the creation of different alert types, making your code easier to maintain and extend.
When to Use Factory Method ✅
- Decoupling Object Creation: Hide complex instantiation logic (e.g., parsing JSON into models).
- Supporting Multiple Platforms: Create different UIs for iOS/macOS without changing client code.
- Testing: Swap real factories with mocks in unit tests.
UIKit Example: Dynamic TableView Cells
protocol CellFactory {
func createCell(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell
}
class PostCellFactory: CellFactory {
func createCell(_ tableView: UITableView, indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath)
// Configure post cell
return cell
}
}
// In your ViewController:
let factory: CellFactory = PostCellFactory()
let cell = factory.createCell(tableView, indexPath: indexPath)
Using a factory pattern here makes it easy to swap out cell types or make changes to the cell configuration without touching the client code.
When Not to Use It 🚫
While the Factory Method is powerful, it’s not always the best solution. Here’s when you might want to skip it:
- Simple Object Creation: If you’re just initializing a basic object like a
String
, you don’t need a factory. - Overengineering: Avoid creating factory hierarchies for a single object type — it might lead to unnecessary complexity.
Swift Best Practices
To get the most out of the Factory Method, consider these best practices:
- Leverage Protocols:
protocol ServiceFactory {
func makeNetworkService() -> NetworkService
}
2. Use Associated Types:
protocol Factory {
associatedtype Product
func create() -> Product
}
3. Combine with Dependency Injection:
class ViewModel {
private let serviceFactory: ServiceFactory
init(serviceFactory: ServiceFactory = DefaultServiceFactory()) {
self.serviceFactory = serviceFactory
}
}
These practices help keep your code clean, modular, and more flexible for testing and future changes.
Refactor Challenge: From Chaos to Factory
Let’s look at a common example. Here’s what the code might look like before applying the Factory Method:
Before (Brittle Code):
class PaymentProcessor {
func processPayment(type: String) {
switch type {
case "creditCard": CreditCardProcessor().process()
case "paypal": PayPalProcessor().process()
// 10 more cases...
}
}
}
This can quickly become hard to maintain and scale. Now, let’s refactor it using the Factory Method.
After (Factory Method FTW):
protocol PaymentProcessorFactory {
func create() -> PaymentProcessor
}
class CreditCardProcessorFactory: PaymentProcessorFactory {
func create() -> PaymentProcessor { CreditCardProcessor() }
}
class PayPalProcessorFactory: PaymentProcessorFactory {
func create() -> PaymentProcessor { PayPalProcessor() }
}
// Client code:
let factory: PaymentProcessorFactory = CreditCardProcessorFactory()
let processor = factory.create()
processor.process()
Now, adding new payment methods becomes much easier, as you only need to create new factories rather than modifying the existing code.
Key Takeaway
The Factory Method pattern is a great tool for writing decoupled, scalable code. Use it when:
- Object creation logic is prone to change.
- You need to support multiple variants of a product.
- Testability is a priority.
Up Next: The Builder pattern — because sometimes, creating an object feels like assembling IKEA furniture without instructions. (Spoiler: URLRequest
is a low-key Builder!)
Pro Tip: Check SwiftUI’s ViewBuilder
implementation. It’s a masterclass in factory patterns!
Next Article: [Builder — Crafting Complex Objects Like a Swift Michelangelo]
Follow to stay in the pattern loop!