PHP策略模式深度拆解:从“硬编码噩梦”到“算法自由切换”的实战指南
目录导读
- 策略模式是什么?—— 用“支付场景”3分钟建立直觉
- 为什么不用if-else?—— 违反开闭原则的代价
- 核心三要素解剖:Context(上下文)、Strategy(策略)、ConcreteStrategy(具体策略)
- PHP代码实战:从接口设计到动态调用(附完整可运行示例)
- 策略模式与状态模式、简单工厂的区别(高频面试坑)
- Laravel框架中的策略模式影子:表单验证与支付网关
- 常见误区与性能考量:策略类膨胀问题如何解决?
- 问答环节:解决你对策略模式最后的5个疑惑
策略模式是什么?—— 用“支付场景”3分钟建立直觉
假设你在开发一个电商系统,用户结账时,需要根据支付方式(微信、支付宝、银行卡)执行不同的逻辑:计算手续费、生成不同格式的订单、调用不同API。

新手写法:在一个PayService类里写满if ($type == 'wechat') {...} elseif ($type == 'alipay') {...}。
噩梦开始:一个月后新增“花呗分期”,你需要修改核心类;每次改动都要回归测试所有支付方式,这违反了“开闭原则”(对扩展开放,对修改关闭)。
策略模式解法:把“每一种支付方式”封装成独立的策略类,支付时,客户端(Context)只需持有当前策略对象的引用,调用统一的方法(如pay()),你想添加新支付方式?创建新类即可,无需碰旧代码。
一句话直觉:策略模式就是“把算法族分别封装,让它们可以互相替换,让算法的变化独立于使用算法的客户”。
为什么不用if-else?—— 违反开闭原则的代价
看看那些反面教材的硬伤:
- 可读性崩溃:当条件超过5个,函数变得冗长,新人无法快速掌握全貌。
- 重复代码:每个分支里可能需要初始化不同日志、不同格式化工具,这些初始化代码在多个分支中重复。
- 单元测试困难:你无法单独测试“支付宝逻辑”,必须模拟所有外部依赖并构造完整上下文。
- 隐藏的耦合:当支付类型增加时,你需要读懂整个if-else链,定位插入点,风险极高。
策略模式通过多态替代条件判断,将“选择”行为放入工厂或配置容器,核心业务逻辑变得纯粹。
核心三要素解剖
- Strategy(策略接口):定义公共算法接口,例如
interface PaymentStrategy { public function pay(Order $order); }。 - ConcreteStrategy(具体策略):实现接口,如
WechatPayStrategy、AlipayStrategy。 - Context(上下文):持有策略引用,负责调用策略方法,它不关心策略内部如何实现,只面向接口编程。
class CheckoutContext {
private PaymentStrategy $strategy;
public function __construct(PaymentStrategy $strategy) {
$this->strategy = $strategy;
}
public function executePayment(Order $order): void {
$this->strategy->pay($order);
}
}
这里的关键是组合优于继承,Context通过构造器或setter注入策略,实现运行时替换。
PHP代码实战:完整可运行示例
假设我们有三类支付策略,并且有对应的参数(如微信的appId)。
// 策略接口
interface PaymentStrategy {
public function pay(Order $order): array;
}
// 具体策略A:微信
class WechatPay implements PaymentStrategy {
public function __construct(private string $appId) {}
public function pay(Order $order): array {
// 模拟调用微信API,生成预支付单
return ['channel' => 'wechat', 'params' => ['appid' => $this->appId, 'order_no' => $order->id]];
}
}
// 具体策略B:支付宝
class AlipayPay implements PaymentStrategy {
public function __construct(private string $appKey) {}
public function pay(Order $order): array {
// 模拟签名及请求
return ['channel' => 'alipay', 'sign' => md5($this->appKey . $order->amount)];
}
}
// 上下文:负责调度的“前台”
class PaymentContext {
public function __construct(private PaymentStrategy $strategy) {}
public function setStrategy(PaymentStrategy $strategy): void { $this->strategy = $strategy; }
public function process(Order $order): array {
return $this->strategy->pay($order);
}
}
// 客户端调用(可以通过工厂或配置决定策略)
$wechat = new WechatPay('wx123456');
$ctx = new PaymentContext($wechat);
$result = $ctx->process(new Order(1001, 199.00));
print_r($result);
// 切换策略(运行时替换)
$alipay = new AlipayPay('alipay_key_2024');
$ctx->setStrategy($alipay);
print_r($ctx->process(new Order(1002, 299.00)));
关键点:客户端完全不接触策略细节,如果后续增加“信用卡”,只需implements PaymentStrategy,修改工厂或容器配置即可。
策略模式与状态模式、简单工厂的区别
这是面试高频陷阱,务必分清:
| 对比项 | 策略模式 | 状态模式 | 简单工厂 |
|---|---|---|---|
| 意图 | 算法互换 | 状态驱动的行为转换 | 创建对象 |
| 谁决定切换 | 客户端/上下文 | 状态类自身(当状态变化时) | 工厂类 |
| 关系 | 策略互不相关 | 状态有依赖(状态转移) | 生成策略或产品 |
| 典型场景 | 日志格式、排序算法、支付 | 订单状态流转(待付款→已付款) | 根据条件创建实例 |
误区提醒:策略模式里策略之间是平等的,可任意替换;而状态模式中状态是有限的,且状态转换有严格规则。
Laravel框架中的策略模式影子
你可能每天在用但没察觉:
- 表单验证:Laravel的
Validator扩展中,Rule接口就是策略。required,email,unique都是独立的策略类。 - 支付网关:Laravel Cashier(Stripe)或第三方扩展包(如
yansongda/pay)内部大量使用策略模式,将微信、支付宝、银联实现为不同策略。 - 广播驱动:
BroadcastManager根据driver配置选择RedisBroadcaster或LogBroadcaster——这就是策略选择的典型实现。
在应用架构中,任何“行为可替换”的地方,都是策略模式的用武之地。
常见误区与性能考量:策略类膨胀问题如何解决?
- 类数量过多:当算法变多时,策略类会膨胀,解决:使用匿名类(PHP 7+),或结合容器结合闭包动态生成策略。
- 过度设计:如果只有2-3种算法且未来几乎不变,别用策略模式,先写if-else,等第二次重构时再提取策略。
- 性能开销:每次调用多了一层方法栈,但PHP的微优化可忽略,真正要关注的是策略对象的重复创建——建议使用单例或复用已实例化策略。
高级技巧:通过依赖注入容器,将策略名映射到类,如$container['pay.wechat'],实现零硬编码。
问答环节:解决你对策略模式最后的5个疑惑
问1:策略模式一定需要接口吗?
答:不一定,如果PHP版本不支持接口或团队风格,可以用抽象类,但接口更推荐,因为它强调“能力契约”而非“共性实现”。
问2:Context中能不能直接new具体策略?
答:可以,但会违反“依赖倒置”,最好通过工厂、容器或参数传入,例如在控制器中根据request->input('pay_method')从配置文件映射策略类。
问3:策略模式与委托模式(Delegate)有什么区别?
答:委托是一种对象组合关系,而策略模式是委托的一种具体应用,侧重“算法互换”,委托更通用,策略更聚焦。
问4:如何在业务中动态决定用哪个策略?
答:典型的做法是创建一个PaymentStrategyFactory,根据入参(如'alipay')返回对应策略实例,这个工厂可以结合match表达式或switch,它只是决策点,不会导致业务逻辑混乱。
问5:策略模式能配合Laravel Pipeline使用吗?
答:可以,Pipeline中的pipe类实现了类似策略的职责,但Pipeline更偏向于“过滤/处理链”,而策略模式是“单一算法选择”,它们可以结合:中间件可以包含策略逻辑。
延伸思考:当你下次面对一堆if-else时,先问自己:“这些分支是否在描述不同的算法?”如果答案是肯定的,为它们定义统一的接口,然后用策略模式重构——你将收获更干净的代码、更轻松的测试,以及更自信的架构演进。