告别 "if-else 地狱":PHP 中的策略模式、管道模式与责任链模式实战
"代码一开始很干净,直到产品经理说:'加一个条件判断就行'。"
如果你写过超过两年的 PHP 业务代码,一定见过这样的场景:一个 processOrder() 方法里塞满了层层嵌套的 if-elseif-else,像一棵不断生长的荆棘树,每次改动都提心吊胆,生怕哪根刺扎到旧逻辑。
这就是传说中的 "if-else 地狱" 。今天我们用三个设计模式------策略模式、管道模式、责任链模式------来拆掉这棵荆棘树,用 PHP 实战案例展示每种模式的适用场景和落地方式。
一、if-else 地狱长什么样?
先看一个典型的"地狱级"代码:
php
// ❌ 地狱级 if-else
public function handleOrder(Order $order)
{
if ($order->type === 'normal') {
if ($order->amount > 1000) {
// 大额普通订单逻辑
if ($order->user->isVip()) {
// VIP 大额...
} else {
// 普通大额...
}
} else {
// 小额普通订单
if ($order->coupon) {
// 有优惠券...
}
}
} elseif ($order->type === 'preorder') {
// 预售订单...
} elseif ($order->type === 'group') {
// 拼团订单...
} elseif ($order->type === 'flash') {
// 秒杀订单...
} else {
throw new \Exception('未知订单类型');
}
}
问题在哪?
| 痛点 | 说明 |
|---|---|
| 违反开闭原则 | 每加一种订单类型就要改这个方法 |
| 违反单一职责 | 一个方法承担了所有类型的处理逻辑 |
| 难以测试 | 要覆盖所有分支组合,测试用例爆炸 |
| 可读性差 | 嵌套层级深,新人看了直接懵 |
二、策略模式:让每种算法各就各位
核心思想
定义一系列算法,把它们封装起来,使它们可以互相替换。
策略模式最适合解决 "同一行为,不同实现" 的场景。比如:订单处理、支付方式、运费计算、折扣策略等。
实战:订单处理策略
1. 定义策略接口
php
interface OrderStrategy
{
public function handle(Order $order): OrderResult;
public function supports(string $type): bool;
}
2. 实现具体策略
php
class NormalOrderStrategy implements OrderStrategy
{
public function supports(string $type): bool
{
return $type === 'normal';
}
public function handle(Order $order): OrderResult
{
// 普通订单的处理逻辑,干净、专注
if ($order->amount > 1000) {
return $this->handleLargeOrder($order);
}
return $this->handleRegularOrder($order);
}
private function handleLargeOrder(Order $order): OrderResult
{
// 大额逻辑...
}
private function handleRegularOrder(Order $order): OrderResult
{
// 普通逻辑...
}
}
php
class GroupOrderStrategy implements OrderStrategy
{
public function supports(string $type): bool
{
return $type === 'group';
}
public function handle(Order $order): OrderResult
{
// 拼团订单的处理逻辑,完全独立
// 就算这里改崩了,也不影响其他订单类型
}
}
php
class FlashOrderStrategy implements OrderStrategy
{
public function supports(string $type): bool
{
return $type === 'flash';
}
public function handle(Order $order): OrderResult
{
// 秒杀订单的处理逻辑
}
}
3. 策略上下文(调度器)
php
class OrderStrategyContext
{
/** @var OrderStrategy[] */
private array $strategies;
public function __construct(iterable $strategies)
{
$this->strategies = $strategies instanceof Traversable
? iterator_to_array($strategies)
: $strategies;
}
public function handle(Order $order): OrderResult
{
foreach ($this->strategies as $strategy) {
if ($strategy->supports($order->type)) {
return $strategy->handle($order);
}
}
throw new \InvalidArgumentException("不支持的订单类型: {$order->type}");
}
}
4. 在 Laravel 中使用(依赖注入)
php
// AppServiceProvider.php
public function register()
{
$this->app->tag([
NormalOrderStrategy::class,
GroupOrderStrategy::class,
FlashOrderStrategy::class,
PreorderStrategy::class,
], 'order_strategies');
$this->app->bind(OrderStrategyContext::class, function ($app) {
return new OrderStrategyContext(
$app->tagged('order_strategies')
);
});
}
php
// Controller 中
public function process(Order $order, OrderStrategyContext $context)
{
$result = $context->handle($order);
return response()->json($result);
}
策略模式效果对比
scss
之前:一个 200 行的 handleOrder(),改一处怕全局
之后:每个 Strategy 独立文件,新增类型只需加一个类
| 维度 | 之前 | 之后 |
|---|---|---|
| 新增订单类型 | 修改原有方法 | 新增一个类 |
| 测试 | 需要 mock 所有分支 | 每个策略独立测试 |
| 代码审查 | 要通读整个方法 | 只看新增的策略类 |
| 新人上手 | 先理解所有分支 | 看对应类型即可 |
三、管道模式:像流水线一样处理请求
核心思想
将复杂的处理过程拆解为一系列独立的步骤,数据像水流一样依次穿过每个步骤。
管道模式(Pipeline)特别适合 "一个请求需要经过多个处理步骤" 的场景:请求中间件、数据过滤/转换、表单验证链、订单处理流水线等。
Laravel 框架本身大量使用管道模式------中间件就是最典型的例子。
实战:订单数据处理管道
假设一个订单创建时,需要经过:参数校验 → 数据补全 → 价格计算 → 库存锁定 → 持久化。
1. 定义管道中间件接口
php
interface OrderPipe
{
public function handle(OrderContext $context, \Closure $next): OrderContext;
}
2. 实现各个管道环节
php
class ValidateOrderPipe implements OrderPipe
{
public function handle(OrderContext $context, \Closure $next): OrderContext
{
$data = $context->getData();
if (empty($data['product_id'])) {
throw new \InvalidArgumentException('商品ID不能为空');
}
if ($data['quantity'] <= 0) {
throw new \InvalidArgumentException('购买数量必须大于0');
}
return $next($context);
}
}
php
class EnrichOrderPipe implements OrderPipe
{
public function handle(OrderContext $context, \Closure $next): OrderContext
{
$data = $context->getData();
// 补充商品信息
$product = Product::find($data['product_id']);
$context->set('product', $product);
$context->set('unit_price', $product->price);
return $next($context);
}
}
php
class CalculatePricePipe implements OrderPipe
{
public function handle(OrderContext $context, \Closure $next): OrderContext
{
$total = $context->get('unit_price') * $context->get('quantity');
// 应用折扣
$discount = $this->calculateDiscount($context);
$context->set('discount', $discount);
$context->set('total_amount', $total - $discount);
return $next($context);
}
private function calculateDiscount(OrderContext $context): float
{
// 折扣逻辑...
return 0;
}
}
php
class LockInventoryPipe implements OrderPipe
{
public function handle(OrderContext $context, \Closure $next): OrderContext
{
$product = $context->get('product');
$quantity = $context->get('quantity');
if ($product->stock < $quantity) {
throw new \RuntimeException('库存不足');
}
// 锁定库存(Redis 或数据库行锁)
Inventory::lock($product->id, $quantity);
return $next($context);
}
}
php
class PersistOrderPipe implements OrderPipe
{
public function handle(OrderContext $context, \Closure $next): OrderContext
{
$order = Order::create([
'user_id' => $context->get('user_id'),
'product_id' => $context->get('product_id'),
'quantity' => $context->get('quantity'),
'total_amount' => $context->get('total_amount'),
'status' => 'pending',
]);
$context->set('order', $order);
return $next($context);
}
}
3. 管道调度器
php
class OrderPipeline
{
/** @var OrderPipe[] */
private array $pipes;
public function __construct(array $pipes)
{
$this->pipes = $pipes;
}
public function process(OrderContext $context): OrderContext
{
$pipeline = array_reduce(
array_reverse($this->pipes),
function ($next, OrderPipe $pipe) {
return function ($context) use ($pipe, $next) {
return $pipe->handle($context, $next);
};
},
function ($context) {
return $context; // 最终闭包
}
);
return $pipeline($context);
}
}
4. 使用
ini
$pipeline = new OrderPipeline([
new ValidateOrderPipe(),
new EnrichOrderPipe(),
new CalculatePricePipe(),
new LockInventoryPipe(),
new PersistOrderPipe(),
]);
$context = new OrderContext($request->validated());
$result = $pipeline->process($context);
$order = $result->get('order');
Laravel 原生 Pipeline 写法
如果你用 Laravel,其实框架已经内置了 Pipeline:
php
use Illuminate\Pipeline\Pipeline;
$order = app(Pipeline::class)
->send($context)
->through([
ValidateOrderPipe::class,
EnrichOrderPipe::class,
CalculatePricePipe::class,
LockInventoryPipe::class,
PersistOrderPipe::class,
])
->then(function ($context) {
return $context->get('order');
});
管道模式效果
scss
之前:一个 createOrder() 方法 150 行,校验/计算/持久化混在一起
之后:每个 Pipe 只做一件事,可以随意组合、复用、调整顺序
四、责任链模式:让请求找到它的处理者
核心思想
使多个对象都有机会处理请求,从而避免发送者和接收者的耦合。将对象连成一条链,请求沿着链传递,直到有一个对象处理它。
责任链模式适合 "多个处理器,谁有能力谁处理" 的场景:审批流、客服工单分配、异常处理、中间件链。
实战:订单审批流程
不同金额的订单需要不同级别的审批:
| 金额范围 | 审批人 |
|---|---|
| ≤ 1000 | 组长 |
| 1000 ~ 5000 | 经理 |
| 5000 ~ 20000 | 总监 |
| > 20000 | CEO |
1. 定义审批处理器
php
abstract class Approver
{
protected ?Approver $next = null;
public function setNext(Approver $approver): Approver
{
$this->next = $approver;
return $approver;
}
abstract public function approve(Order $order): ?ApprovalResult;
}
2. 实现各级审批人
scala
class TeamLeadApprover extends Approver
{
public function approve(Order $order): ?ApprovalResult
{
if ($order->amount <= 1000) {
return new ApprovalResult('approved', '组长审批通过');
}
if ($this->next) {
return $this->next->approve($order);
}
return null;
}
}
php
class ManagerApprover extends Approver
{
public function approve(Order $order): ?ApprovalResult
{
if ($order->amount <= 5000) {
// 经理额外逻辑:检查预算
if ($this->checkBudget($order)) {
return new ApprovalResult('approved', '经理审批通过');
}
return new ApprovalResult('rejected', '超出部门预算');
}
if ($this->next) {
return $this->next->approve($order);
}
return null;
}
private function checkBudget(Order $order): bool
{
// 预算检查逻辑...
return true;
}
}
scala
class DirectorApprover extends Approver
{
public function approve(Order $order): ?ApprovalResult
{
if ($order->amount <= 20000) {
return new ApprovalResult('approved', '总监审批通过');
}
if ($this->next) {
return $this->next->approve($order);
}
return null;
}
}
scala
class CEOApprover extends Approver
{
public function approve(Order $order): ?ApprovalResult
{
// CEO 处理所有剩余情况
return new ApprovalResult('approved', 'CEO 审批通过');
}
}
3. 组装责任链
php
class ApprovalChain
{
private Approver $chain;
public function __construct()
{
$teamLead = new TeamLeadApprover();
$manager = new ManagerApprover();
$director = new DirectorApprover();
$ceo = new CEOApprover();
// 组装链条:组长 → 经理 → 总监 → CEO
$teamLead->setNext($manager);
$manager->setNext($director);
$director->setNext($ceo);
$this->chain = $teamLead;
}
public function process(Order $order): ApprovalResult
{
$result = $this->chain->approve($order);
if (!$result) {
throw new \RuntimeException('无人能审批此订单');
}
return $result;
}
}
4. 使用
ini
$chain = new ApprovalChain();
$result = $chain->process($order);
echo $result->getMessage(); // "经理审批通过"
责任链模式效果
php
之前:if ($amount <= 1000) { ... } elseif ($amount <= 5000) { ... } else { ... }
之后:新增审批级别只需加一个类,调整金额阈值只改对应类
五、三种模式怎么选?
这是最关键的问题。三个模式都能消除 if-else,但适用场景不同:
| 模式 | 核心隐喻 | 适合场景 | 关键特征 |
|---|---|---|---|
| 策略模式 | 菜单点餐 | 同一行为的不同算法实现 | 互斥选择,选一个策略执行 |
| 管道模式 | 工厂流水线 | 一个请求经过多道工序 | 顺序执行,每步都参与 |
| 责任链模式 | 击鼓传花 | 多个处理器,谁行谁上 | 链式传递,一个处理就停 |
决策树
arduino
你的 if-else 在做什么?
│
├─ 根据类型选择不同的处理方式 → 策略模式
│ └─ 例:支付网关选择、运费计算、导出格式
│
├─ 一个请求需要依次经过多个处理步骤 → 管道模式
│ └─ 例:表单处理、订单创建流程、API 请求中间件
│
└─ 多个处理者,按条件逐级判断谁能处理 → 责任链模式
└─ 例:审批流、异常处理器、日志级别处理
组合使用
实际项目中,这三种模式经常组合出现。比如一个完整的订单系统:
arduino
┌─────────────────────────────────────────────┐
│ OrderService::create() │
│ │
│ 1. 管道模式:数据校验 → 补全 → 计算 → 持久化 │
│ │ │
│ ├─ 计算价格步骤内部 → 策略模式 │
│ │ ├─ VIP 价格策略 │
│ │ ├─ 促销价格策略 │
│ │ └─ 普通价格策略 │
│ │ │
│ └─ 持久化后 → 责任链模式触发通知 │
│ ├─ 短信通知 │
│ ├─ 邮件通知 │
│ └─ App 推送 │
└─────────────────────────────────────────────┘
六、总结
设计模式不是银弹,但它是你对抗代码腐烂的武器库。
| 你获得的能力 | 说明 |
|---|---|
| 消除 if-else 地狱 | 不再有 5 层嵌套的条件判断 |
| 拥抱开闭原则 | 新增功能靠"加"而不是靠"改" |
| 代码可测试性 | 每个类独立测试,mock 成本极低 |
| 团队协作友好 | 每个人改自己的类,减少冲突 |
最后一句忠告 :不要为了用模式而用模式。如果一个 if-else 只有两个分支且永远不会变,那就老老实实写 if-else。设计模式的目的是管理复杂度 ,而不是制造过度设计。