本文目录导读:

在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个简单的
策略模式的核心是封装变化,将稳定的算法接口与变化的实现分离,使系统更灵活、更易于扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播