PHP项目策略模式如何替换分支判断

wen PHP项目 22

本文目录导读:

PHP项目策略模式如何替换分支判断

  1. 场景:订单折扣计算
  2. 重构:应用策略模式
  3. 进一步优化:动态注册与依赖注入
  4. 策略模式的优缺点总结
  5. 最佳实践建议

在PHP项目中使用策略模式替换分支判断(如大量的if-elseswitch)的核心思路是:将每个分支的逻辑封装成独立的策略类,然后通过上下文类或客户端来选择并执行对应的策略

下面通过一个具体的例子来展示如何用策略模式重构分支判断。


场景:订单折扣计算

假设一个电商系统,根据用户的会员等级计算折扣:

  • 普通会员:无折扣
  • 白银会员: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('未知会员等级');
    }
}

问题

  1. 每次增加新会员等级,都需要修改这个方法,违反开闭原则。
  2. 逻辑杂糅在一起(满减逻辑混在折扣里),难以维护和测试。

重构:应用策略模式

第一步:定义策略接口

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可能更清晰

最佳实践建议

  1. 何时用策略模式

    • 分支判断逻辑可能频繁变更(增删改)
    • 不同分支逻辑复杂、相互独立
    • 需要复用同一段逻辑(不同名称对应相同算法)
  2. 如何选择策略

    • 使用简单的数组映射(如上面示例)
    • 结合工厂模式或服务容器自动解析
    • 根据数据库配置或用户输入动态选择
  3. 避免过度抽象

    • 如果分支逻辑只有2-3个简单的return,保留if-else可能更清晰
    • 建议在第三次面临类似分支扩展时开始重构

策略模式的核心是封装变化,将稳定的算法接口与变化的实现分离,使系统更灵活、更易于扩展。

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