PHP策略模式怎么理解

wen PHP项目 5

PHP策略模式深度拆解:从“硬编码噩梦”到“算法自由切换”的实战指南


目录导读

  1. 策略模式是什么?—— 用“支付场景”3分钟建立直觉
  2. 为什么不用if-else?—— 违反开闭原则的代价
  3. 核心三要素解剖:Context(上下文)、Strategy(策略)、ConcreteStrategy(具体策略)
  4. PHP代码实战:从接口设计到动态调用(附完整可运行示例)
  5. 策略模式与状态模式、简单工厂的区别(高频面试坑)
  6. Laravel框架中的策略模式影子:表单验证与支付网关
  7. 常见误区与性能考量:策略类膨胀问题如何解决?
  8. 问答环节:解决你对策略模式最后的5个疑惑

策略模式是什么?—— 用“支付场景”3分钟建立直觉

假设你在开发一个电商系统,用户结账时,需要根据支付方式(微信、支付宝、银行卡)执行不同的逻辑:计算手续费、生成不同格式的订单、调用不同API。

PHP策略模式怎么理解

新手写法:在一个PayService类里写满if ($type == 'wechat') {...} elseif ($type == 'alipay') {...}
噩梦开始:一个月后新增“花呗分期”,你需要修改核心类;每次改动都要回归测试所有支付方式,这违反了“开闭原则”(对扩展开放,对修改关闭)。

策略模式解法:把“每一种支付方式”封装成独立的策略类,支付时,客户端(Context)只需持有当前策略对象的引用,调用统一的方法(如pay()),你想添加新支付方式?创建新类即可,无需碰旧代码。

一句话直觉:策略模式就是“把算法族分别封装,让它们可以互相替换,让算法的变化独立于使用算法的客户”。

为什么不用if-else?—— 违反开闭原则的代价

看看那些反面教材的硬伤:

  • 可读性崩溃:当条件超过5个,函数变得冗长,新人无法快速掌握全貌。
  • 重复代码:每个分支里可能需要初始化不同日志、不同格式化工具,这些初始化代码在多个分支中重复。
  • 单元测试困难:你无法单独测试“支付宝逻辑”,必须模拟所有外部依赖并构造完整上下文。
  • 隐藏的耦合:当支付类型增加时,你需要读懂整个if-else链,定位插入点,风险极高。

策略模式通过多态替代条件判断,将“选择”行为放入工厂或配置容器,核心业务逻辑变得纯粹。

核心三要素解剖

  • Strategy(策略接口):定义公共算法接口,例如interface PaymentStrategy { public function pay(Order $order); }
  • ConcreteStrategy(具体策略):实现接口,如WechatPayStrategyAlipayStrategy
  • 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配置选择RedisBroadcasterLogBroadcaster——这就是策略选择的典型实现。

在应用架构中,任何“行为可替换”的地方,都是策略模式的用武之地。

常见误区与性能考量:策略类膨胀问题如何解决?

  • 类数量过多:当算法变多时,策略类会膨胀,解决:使用匿名类(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时,先问自己:“这些分支是否在描述不同的算法?”如果答案是肯定的,为它们定义统一的接口,然后用策略模式重构——你将收获更干净的代码、更轻松的测试,以及更自信的架构演进。

抱歉,评论功能暂时关闭!