策略模式
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 74 课,共 91 课。
策略模式是一种行为型设计模式,可以让你定义一组算法,将每种算法封装在自己的类中,并在运行时互换使用。使用该策略的对象不需要知道自己正在运行哪种算法。
想象一个可以应用不同折扣策略的购物车。你不必用条件语句硬编码折扣逻辑,而是将每种折扣定义为一个单独的类:
<?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);
}
}
上下文类接受任意策略,并委托其进行计算:
<?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);
输出:
100
80当你有多种方式来执行某项操作,并且希望在它们之间切换,同时不修改使用这些方式的类时,策略模式就能发挥作用。添加一种新的折扣类型,只需创建一个新类,无需修改现有代码。
挑战
简单让我们使用策略模式构建一个运费计算器。不同的配送方式有不同的定价规则:标准配送收取固定费用,快递配送按比例加收费用,隔夜配送则结合这两种方式。策略模式让你可以替换这些计算方式,而无需更改使用它们的代码。
你会将代码分配到 4 个文件中:
ShippingStrategy.php:创建一个ShippingStrategy接口,为所有配送方式定义契约。接口应包含一个calculate(float $weight): float方法,该方法接收包裹重量并返回运费。ShippingMethods.php:引入 ShippingStrategy 文件,并创建 3 个实现该接口的类:StandardShipping:无论重量多少,都收取固定费用$5.00ExpressShipping:每千克收取$2.00(将重量乘以 2)OvernightShipping:收取$10.00的基础费用,另加每千克$3.00
ShippingCalculator.php:引入 ShippingStrategy 文件,并创建一个作为上下文的ShippingCalculator类。计算器应当:- 在构造函数中接收一个
ShippingStrategy(使用构造函数属性提升) - 包含一个
setStrategy(ShippingStrategy $strategy): void方法,用于在运行时更改配送方式 - 包含一个
calculateCost(float $weight): float方法,将计算委托给当前策略
- 在构造函数中接收一个
main.php:引入 ShippingMethods 和 ShippingCalculator 文件。你会收到两个输入:配送类型("standard"、"express"或"overnight")和包裹重量。创建一个从
StandardShipping开始的ShippingCalculator。根据配送类型输入,使用setStrategy()切换到相应的配送方式。然后计算给定重量的运费,并将结果格式化为两位小数后打印出来。输出应为:
Shipping cost: $[cost]
这个挑战展示了策略模式如何让你的配送系统更加灵活:要添加新的配送方式,例如 SameDayShipping,只需创建一个实现该接口的新类,无需修改计算器或现有的配送方式。
亲手试试
<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';
// 读取输入
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));
// TODO: 创建一个初始使用 StandardShipping 的 ShippingCalculator
// TODO: 根据输入的配送类型("standard"、"express" 或 "overnight"),
// 使用 setStrategy() 切换到对应的配送方式
// TODO: 计算给定重量的费用
// TODO: 将结果保留两位小数后输出
// 输出格式:Shipping cost: $[cost]
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器