本文目录导读:

在PHP项目中使用策略模式替换分支判断(如大量的if-else或switch)的核心思路是:将每个分支的逻辑封装成独立的策略类,然后通过上下文类或客户端来选择并执行对应的策略。
下面通过一个具体的例子来展示如何用策略模式重构分支判断。
场景:订单折扣计算
假设一个电商系统,根据用户的会员等级计算折扣:
- 普通会员:无折扣
- 白银会员:9折
- 黄金会员:8折
- 黑金会员:7折(且满100减20)
原始代码(分支判断)
public function calculateDiscount(string $userLevel, float $orderAmount): float {
if ($userLevel === 'normal') {
return $orderAmount;
} elseif ($userLevel === 'silver') {
return $orderAmount * 0.9;
} elseif ($userLevel === 'gold') {
return $orderAmount * 0.8;
} elseif ($userLevel === 'black') {
$discounted = $orderAmount * 0.7;
if ($discounted >= 100) {
$discounted -= 20;
}
return $discounted;
} else {
throw new InvalidArgumentException('未知会员等级');
}
}
问题:
- 每次增加新会员等级,都需要修改这个方法,违反开闭原则。
- 逻辑杂糅在一起(满减逻辑混在折扣里),难以维护和测试。
重构:应用策略模式
第一步:定义策略接口
interface DiscountStrategy {
public function calculate(float $amount): float;
}
第二步:实现具体策略类
class NormalDiscount implements DiscountStrategy {
public function calculate(float $amount): float {
return $amount;
}
}
class SilverDiscount implements DiscountStrategy {
public function calculate(float $amount): float {
return $amount * 0.9;
}
}
class GoldDiscount implements DiscountStrategy {
public function calculate(float $amount): float {
return $amount * 0.8;
}
}
class BlackDiscount implements DiscountStrategy {
public function calculate(float $amount): float {
$discounted = $amount * 0.7;
// 满减逻辑独立封装,易于修改和测试
if ($discounted >= 100) {
$discounted -= 20;
}
return $discounted;
}
}
第三步:创建上下文类(或直接在使用处选择策略)
class OrderDiscountCalculator {
private array $strategies = [];
public function __construct() {
// 注册策略(可用配置、数据库、服务容器等方式注册)
$this->strategies = [
'normal' => new NormalDiscount(),
'silver' => new SilverDiscount(),
'gold' => new GoldDiscount(),
'black' => new BlackDiscount(),
];
}
public function calculate(string $userLevel, float $amount): float {
if (!isset($this->strategies[$userLevel])) {
throw new InvalidArgumentException("未知会员等级: $userLevel");
}
return $this->strategies[$userLevel]->calculate($amount);
}
}
第四步:客户端使用
$calculator = new OrderDiscountCalculator();
$finalAmount = $calculator->calculate('silver', 200); // 180
进一步优化:动态注册与依赖注入
如果要完全消除硬编码的映射,可以引入一个策略注册中心或使用服务容器自动注入:
class DiscountStrategyManager {
private array $strategies = [];
public function register(string $key, DiscountStrategy $strategy): void {
$this->strategies[$key] = $strategy;
}
public function get(string $key): DiscountStrategy {
if (!isset($this->strategies[$key])) {
throw new \RuntimeException("No strategy found for key: $key");
}
return $this->strategies[$key];
}
}
// 在服务提供者或初始化阶段配置
$manager = new DiscountStrategyManager();
$manager->register('normal', new NormalDiscount());
$manager->register('silver', new SilverDiscount());
// ...
// 使用时
$strategy = $manager->get($userLevel);
$finalAmount = $strategy->calculate($orderAmount);
甚至用匿名函数/闭包代替类(适用于简单场景)
$strategies = [
'normal' => fn($amount) => $amount,
'silver' => fn($amount) => $amount * 0.9,
'gold' => fn($amount) => $amount * 0.8,
'black' => function($amount) {
$discounted = $amount * 0.7;
return $discounted >= 100 ? $discounted - 20 : $discounted;
},
];
if (!isset($strategies[$userLevel])) {
throw new \InvalidArgumentException();
}
$finalAmount = $strategies[$userLevel]($orderAmount);
这种方法更轻量,但灵活性略低于类实现(例如无法单独测试策略、无法注入依赖)。
策略模式的优缺点总结
优点
| 方面 | 说明 |
|---|---|
| 开闭原则 | 增加新策略无需修改现有代码(只需新增类并注册) |
| 可测试性 | 每个策略可以独立测试 |
| 可维护性 | 分支逻辑隔离,修改某个策略不影响其他逻辑 |
| 可复用性 | 同一策略可在多个地方使用 |
缺点
| 方面 | 说明 |
|---|---|
| 类数量增加 | 每个分支都需要一个类(可用闭包缓解) |
| 选择逻辑仍需处理 | 客户端仍需要一个注册/查找机制来确定使用哪个策略 |
| 过度设计风险 | 如果分支很少且固定(如2-3个),使用简单if-else可能更清晰 |
最佳实践建议
-
何时用策略模式:
- 分支判断逻辑可能频繁变更(增删改)
- 不同分支逻辑复杂、相互独立
- 需要复用同一段逻辑(不同名称对应相同算法)
-
如何选择策略:
- 使用简单的数组映射(如上面示例)
- 结合工厂模式或服务容器自动解析
- 根据数据库配置或用户输入动态选择
-
避免过度抽象:
- 如果分支逻辑只有2-3个简单的
return,保留if-else可能更清晰 - 建议在第三次面临类似分支扩展时开始重构
- 如果分支逻辑只有2-3个简单的
策略模式的核心是封装变化,将稳定的算法接口与变化的实现分离,使系统更灵活、更易于扩展。