In this tutorial, we'll explore how to implement the Strategy Pattern using Protocol-Oriented Programming (POP) in Swift. We'll use a simple example of calculating shipping costs using different strategies to demonstrate how the pattern works.
This tutorial assumes you have a basic understanding of Swift programming and familiarity with concepts like protocols and structs. No prior knowledge of design patterns is required, as we'll explain the Strategy Pattern step by step.
The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm's behavior at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This allows the algorithm to vary independently from clients that use it.
We'll create an application that calculates shipping costs based on different shipping methods:
Our goal is to make the shipping cost calculation flexible, allowing us to add new shipping methods easily without modifying existing code.
We'll start by defining a protocol ShippingStrategy
that declares a method for calculating the shipping cost:
protocol ShippingStrategy {
func calculateShippingCost(for weight: Double) -> Double
}
Next, we'll create concrete strategies that conform to the ShippingStrategy
protocol.
struct StandardShippingStrategy: ShippingStrategy {
func calculateShippingCost(for weight: Double) -> Double {
return weight * 1.0 // $1 per kg
}
}
struct ExpressShippingStrategy: ShippingStrategy {
func calculateShippingCost(for weight: Double) -> Double {
return weight * 2.0 + 10.0 // $2 per kg plus $10 flat fee
}
}
struct FreeShippingStrategy: ShippingStrategy {
func calculateShippingCost(for weight: Double) -> Double {
return 0.0 // Free shipping
}
}
We'll create a ShippingCostCalculator
that uses a ShippingStrategy
to calculate the shipping cost:
struct ShippingCostCalculator {
var strategy: ShippingStrategy
func calculateCost(for weight: Double) -> Double {
return strategy.calculateShippingCost(for: weight)
}
}
Let's see how we can use the ShippingCostCalculator
with different strategies:
// Define the weight of the package
let packageWeight = 5.0 // in kilograms
// Use Standard Shipping
let standardStrategy = StandardShippingStrategy()
let standardCalculator = ShippingCostCalculator(strategy: standardStrategy)
let standardCost = standardCalculator.calculateCost(for: packageWeight)
print("Standard Shipping Cost: $\(standardCost)") // Output: $5.0
// Use Express Shipping
let expressStrategy = ExpressShippingStrategy()
let expressCalculator = ShippingCostCalculator(strategy: expressStrategy)
let expressCost = expressCalculator.calculateCost(for: packageWeight)
print("Express Shipping Cost: $\(expressCost)") // Output: $20.0
// Use Free Shipping
let freeStrategy = FreeShippingStrategy()
let freeCalculator = ShippingCostCalculator(strategy: freeStrategy)
let freeCost = freeCalculator.calculateCost(for: packageWeight)
print("Free Shipping Cost: $\(freeCost)") // Output: $0.0
Standard Shipping Cost: $5.0
Express Shipping Cost: $20.0
Free Shipping Cost: $0.0
By implementing the Strategy Pattern, we can switch between different shipping cost calculations easily.
Each strategy conforms to the ShippingStrategy
protocol and provides its own implementation of the calculateShippingCost
method.
The ShippingCostCalculator
acts as the context and uses the selected strategy to compute the cost.
Suppose we want to add an International Shipping strategy that charges a base fee plus a rate per kilogram.
struct InternationalShippingStrategy: ShippingStrategy {
func calculateShippingCost(for weight: Double) -> Double {
let baseFee = 15.0
let perKgRate = 3.0
return baseFee + (weight * perKgRate)
}
}
Usage:
let internationalStrategy = InternationalShippingStrategy()
let internationalCalculator = ShippingCostCalculator(strategy: internationalStrategy)
let internationalCost = internationalCalculator.calculateCost(for: packageWeight)
print("International Shipping Cost: $\(internationalCost)") // Output: $30.0
By using the Strategy Pattern, we can design our code to be flexible and maintainable. Protocol-Oriented Programming in Swift makes it easy to define strategies and switch between them. This pattern is not limited to shipping costs; it can be applied to various scenarios where behavior can change at runtime.
You can experiment with the Strategy Pattern by creating more strategies or by applying it to different problems. For example, you could implement discount strategies for a shopping cart or different sorting algorithms for a list.