Menu
CoddyTech

策略模式

本课属于 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

当你有多种方式来执行某项操作,并且希望在它们之间切换,同时不修改使用这些方式的类时,策略模式就能发挥作用。添加一种新的折扣类型,只需创建一个新类,无需修改现有代码。

challenge icon

挑战

简单

让我们使用策略模式构建一个运费计算器。不同的配送方式有不同的定价规则:标准配送收取固定费用,快递配送按比例加收费用,隔夜配送则结合这两种方式。策略模式让你可以替换这些计算方式,而无需更改使用它们的代码。

你会将代码分配到 4 个文件中:

  • ShippingStrategy.php:创建一个 ShippingStrategy 接口,为所有配送方式定义契约。接口应包含一个 calculate(float $weight): float 方法,该方法接收包裹重量并返回运费。
  • ShippingMethods.php:引入 ShippingStrategy 文件,并创建 3 个实现该接口的类:
    • StandardShipping:无论重量多少,都收取固定费用 $5.00
    • ExpressShipping:每千克收取 $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]

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器