
本文介绍在 php 中如何通过策略模式结合依赖注入,根据运行时参数(如支付方式名称)安全、可扩展地选择接口的具体实现,避免违反开闭原则,并提升代码的可维护性与可测试性。
本文介绍在 php 中如何通过策略模式结合依赖注入,根据运行时参数(如支付方式名称)安全、可扩展地选择接口的具体实现,避免违反开闭原则,并提升代码的可维护性与可测试性。
在面向对象设计中,当需要根据外部参数(如 order->getPaymentMethod() 返回的字符串)动态决定使用哪个具体类来执行某项行为时,直接用 match 或 if-else 显式硬编码映射关系(如 'card' => $this->cardPayment)虽简单,但存在明显缺陷:每新增一种支付方式,就必须修改 getPaymentProcessor() 方法——这违反了开闭原则(Open/Closed Principle),也使单元测试难以模拟、扩展成本升高。
更合理的方式是将“选择逻辑”与“执行逻辑”解耦,采用 策略模式(Strategy Pattern) + 依赖注入(Dependency Injection) 的组合方案。核心思想是:让容器负责注册所有策略,业务类仅按需获取并委托执行,而非自行判断和硬绑定。
✅ 推荐实现:基于 DI 容器的策略注册与分发
首先,统一策略接口(注意命名语义清晰,聚焦行为而非实体):
interface PaymentStrategy
{
public function process(Order $order): void;
}
class CardPayment implements PaymentStrategy
{
public function process(Order $order): void
{
// 卡支付逻辑
echo "Processing card payment for order #{$order->getId()}\n";
}
}
class BankTransfer implements PaymentStrategy
{
public function process(Order $order): void
{
// 银行转账逻辑
echo "Processing bank transfer for order #{$order->getId()}\n";
}
}
接着,定义一个策略调度服务,它不持有具体实现,而是接收由 DI 容器注入的完整策略映射表(paymentMethod → Strategy):
class PaymentService
{
private ?PaymentStrategy $currentStrategy = null;
/**
* @param array<string paymentstrategy> $strategies 键为支付方式标识符(如 'card', 'bank')
*/
public function __construct(private array $strategies) {}
/**
* 根据支付方式名称激活对应策略
* @throws InvalidArgumentException 当策略未注册时
*/
public function useStrategy(string $paymentMethod): void
{
if (!isset($this->strategies[$paymentMethod])) {
throw new InvalidArgumentException(
"No payment strategy registered for method: '{$paymentMethod}'"
);
}
$this->currentStrategy = $this->strategies[$paymentMethod];
}
/**
* 执行当前激活策略的处理逻辑
* @throws LogicException 若未调用 useStrategy()
*/
public function process(Order $order): void
{
if ($this->currentStrategy === null) {
throw new LogicException('No payment strategy selected. Call useStrategy() first.');
}
$this->currentStrategy->process($order);
}
}</string>
最后,在 DI 容器配置中完成策略注册(以 Laravel/Symfony/PHP-DI 等主流容器为例):
// 示例:PHP-DI 配置片段(或等效的 YAML/XML/Attribute 配置)
return [
PaymentStrategy::class => \DI\aggregate(
\DI\create(CardPayment::class),
\DI\create(BankTransfer::class),
),
// 关键:显式构建映射数组
'payment_strategies' => \DI\factory(function () {
return [
'card' => \DI\create(CardPayment::class),
'bank' => \DI\create(BankTransfer::class),
// 新增 'paypal'?只需在此添加一行,无需改任何业务逻辑
// 'paypal' => \DI\create(PayPalPayment::class),
];
}),
PaymentService::class => \DI\create()
->constructorParameter('strategies', \DI\get('payment_strategies')),
];
业务层调用变得简洁、稳定且可测试:
class OrderService
{
public function __construct(private PaymentService $paymentService) {}
public function payOrder(Order $order): void
{
$this->paymentService->useStrategy($order->getPaymentMethod());
$this->paymentService->process($order);
}
}
⚠️ 注意事项与最佳实践
- 避免在策略类中暴露标识名:不要让 CardPayment::getName() 返回 'card' 来做运行时匹配——这会引入重复逻辑与潜在一致性风险。标识应由配置层(即 DI 容器)统一管理。
-
类型安全增强:可配合 PHP 8.1+ 枚举(enum PaymentMethod: string)替代字符串字面量,进一步杜绝拼写错误:
enum PaymentMethod: string { case CARD = 'card'; case BANK = 'bank'; } // then use: $service->useStrategy($order->getPaymentMethod()->value); - 性能无虞:映射数组查找是 O(1),远优于反射或服务定位器(Service Locator),且完全符合依赖倒置原则。
- 测试友好:PaymentService 可轻松用 Mock 策略数组进行单元测试;新增策略只需更新配置,零侵入现有代码。
综上,真正的“正确方式”不是在业务逻辑里写分支,而是把选择权交给依赖注入容器,把变化点(新增支付方式)收束到配置层——这才是开闭原则的落地实践,也是现代 PHP 应用架构的推荐范式。











