Strategy Pattern
Part of the Object Oriented Programming section of Coddy's PHP journey — lesson 74 of 91.
The Strategy Pattern is a behavioral design pattern that lets you define a family of algorithms, encapsulate each one in its own class, and make them interchangeable at runtime. The object using the strategy doesn't need to know which algorithm it's running.
Imagine a shopping cart that can apply different discount strategies. Instead of hardcoding discount logic with conditionals, you define each discount as a separate class:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}
class NoDiscount implements DiscountStrategy {
public function calculate(float $total): float {
return $total;
}
}
class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function calculate(float $total): float {
return $total - ($total * $this->percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return max(0, $total - $this->amount);
}
}
The context class accepts any strategy and delegates the calculation:
<?php
class ShoppingCart {
public function __construct(private DiscountStrategy $discount) {}
public function setDiscount(DiscountStrategy $discount): void {
$this->discount = $discount;
}
public function checkout(float $total): float {
return $this->discount->calculate($total);
}
}
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);
Output:
100
80The Strategy Pattern shines when you have multiple ways to perform an operation and want to switch between them without modifying the class that uses them. Adding a new discount type means creating a new class, not touching existing code.
Challenge
EasyLet's build a shipping cost calculator using the Strategy Pattern. Different shipping methods have different pricing rules — standard shipping charges a flat rate, express shipping adds a percentage, and overnight shipping combines both. The Strategy Pattern lets you swap these calculations without changing the code that uses them.
You'll organize your code across four files:
ShippingStrategy.php— Create aShippingStrategyinterface that defines the contract for all shipping methods. It should have a methodcalculate(float $weight): floatthat takes the package weight and returns the shipping cost.ShippingMethods.php— Include the ShippingStrategy file and create three classes that implement the interface:StandardShipping— Charges a flat rate of$5.00regardless of weightExpressShipping— Charges$2.00per kilogram (multiply weight by 2)OvernightShipping— Charges a base fee of$10.00plus$3.00per kilogram
ShippingCalculator.php— Include the ShippingStrategy file and create aShippingCalculatorclass that acts as the context. Your calculator should:- Accept a
ShippingStrategyin its constructor (use constructor promotion) - Have a
setStrategy(ShippingStrategy $strategy): voidmethod to change the shipping method at runtime - Have a
calculateCost(float $weight): floatmethod that delegates to the current strategy
- Accept a
main.php— Include the ShippingMethods and ShippingCalculator files. You'll receive two inputs: a shipping type ("standard","express", or"overnight") and a package weight.Create a
ShippingCalculatorstarting withStandardShipping. Based on the shipping type input, usesetStrategy()to switch to the appropriate shipping method. Then calculate the cost for the given weight and print the result formatted to two decimal places.The output should be:
Shipping cost: $[cost]
This challenge demonstrates how the Strategy Pattern makes your shipping system flexible — adding a new shipping method like SameDayShipping only requires creating a new class that implements the interface, without touching the calculator or existing shipping methods.
Cheat sheet
The Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each one in its own class, and makes them interchangeable at runtime.
Define an interface for the strategy:
<?php
interface DiscountStrategy {
public function calculate(float $total): float;
}Implement concrete strategies:
<?php
class NoDiscount implements DiscountStrategy {
public function calculate(float $total): float {
return $total;
}
}
class PercentageDiscount implements DiscountStrategy {
public function __construct(private int $percent) {}
public function calculate(float $total): float {
return $total - ($total * $this->percent / 100);
}
}
class FixedDiscount implements DiscountStrategy {
public function __construct(private float $amount) {}
public function calculate(float $total): float {
return max(0, $total - $this->amount);
}
}Create a context class that uses the strategy:
<?php
class ShoppingCart {
public function __construct(private DiscountStrategy $discount) {}
public function setDiscount(DiscountStrategy $discount): void {
$this->discount = $discount;
}
public function checkout(float $total): float {
return $this->discount->calculate($total);
}
}Use the pattern by injecting strategies:
<?php
$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100); // 100
$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100); // 80The Strategy Pattern allows you to switch between algorithms without modifying the class that uses them. Adding new strategies only requires creating new classes that implement the interface.
Try it yourself
<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';
// Read input
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));
// TODO: Create a ShippingCalculator starting with StandardShipping
// TODO: Based on the shipping type input ("standard", "express", or "overnight"),
// use setStrategy() to switch to the appropriate shipping method
// TODO: Calculate the cost for the given weight
// TODO: Print the result formatted to two decimal places
// Output format: Shipping cost: $[cost]
?>This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe $this KeywordMethodsPropertiesConstructor (__construct)Destructor (__destruct)Recap - Simple Calculator4Inheritance
Basic InheritanceThe parent:: KeywordMethod OverridingThe final KeywordAbstract ClassesRecap - Employee Hierarchy7Encapsulation
Public, Protected, PrivateAccess Modifiers in DepthGetters and SettersInformation HidingConstructor Promotion (8.0)Recap - Student Records System2Namespaces & Autoloading
Introduction to NamespacesThe use KeywordPSR-4 Autoloading StandardComposer AutoloaderRecap - Organized Project5Interfaces & Contracts
Introduction to InterfacesImplementing InterfacesMultiple Interface ImplementInterface vs Abstract ClassType Hinting with InterfacesRecap - Shape Calculator8Magic Methods
Magic Methods Introduction__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Object Cloning__serialize & __unserializeRecap - Custom Collection11Type System & Error Handling
Type DeclarationsNullable TypesUnion & Intersection TypesException ClassesCustom Exception HierarchyTry, Catch, FinallyRecap - Form Validator14Project: Library Management
Project OverviewBook and User Classes3Class Properties
Instance vs Static PropertiesConstants in ClassesStatic Methods & PropertiesPrivate & Protected PropertiesReadonly Properties (PHP 8.1)Recap - Bank Account Manager6Polymorphism
Method Overriding RevisitedPolymorphism via InterfacesType Hinting & Union TypesLate Static BindingRecap - Payment Processor9Traits
Introduction to TraitsUsing Multiple TraitsTrait Conflict ResolutionAbstract Methods in TraitsTraits vs Inheritance12Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern