Card 04/ 09
Strategy Example: Shipping Costs per Carrier
Standard, express and collection-point delivery, chosen at checkout.
Shipping costs at a checkout are a natural Strategy, and this example maps the template onto it. Each carrier prices a parcel its own way: standard charges £3.99 plus £1 per kilogram, express charges £8.99 plus £2 per kilogram, and a collection point charges a flat £1.99. Weight is rounded up to the next whole kilogram, and money is held in pence so no total suffers from floating-point rounding.
| Template | Shipping example |
|---|---|
| Strategy | Shipping, with one method, cost_pence(weight_kg) |
| ConcreteStrategy | Standard, Express and CollectionPoint |
| Context | Checkout, which adds the shipping cost to the price of the goods |
| Client | The loop at the bottom, which looks up the customer's choice and passes it in |
import math
from abc import ABC, abstractmethod
class Shipping(ABC):
@abstractmethod
def cost_pence(self, weight_kg: float) -> int: ...
class Standard(Shipping):
def cost_pence(self, weight_kg: float) -> int:
return 399 + 100 * math.ceil(weight_kg)
class Express(Shipping):
def cost_pence(self, weight_kg: float) -> int:
return 899 + 200 * math.ceil(weight_kg)
class CollectionPoint(Shipping):
def cost_pence(self, weight_kg: float) -> int:
return 199
class Checkout:
def __init__(self, shipping: Shipping) -> None:
self._shipping = shipping
def total_pence(self, goods_pence: int, weight_kg: float) -> int:
return goods_pence + self._shipping.cost_pence(weight_kg)
CARRIERS: dict[str, Shipping] = {
"standard": Standard(),
"express": Express(),
"collection": CollectionPoint(),
}
for choice in ("standard", "express", "collection"):
total = Checkout(CARRIERS[choice]).total_pence(goods_pence=4200, weight_kg=2.5)
print(f"{choice}: £{total // 100}.{total % 100:02d}")standard: £48.99
express: £56.99
collection: £43.99The 2.5 kg parcel is charged as 3 kg, so standard costs 399 + 300 pence and express 899 + 600, while the collection point ignores the weight. Checkout.total_pence is one line, and it is the same line for all three.
Notice where the customer's choice is turned into an object: the CARRIERS dictionary maps the name chosen at checkout to a strategy. The lookup replaces the if chain, and an unknown name raises a KeyError at once rather than returning None. Adding a fourth carrier means one class and one dictionary entry.
← → move through sections and cards