PHP 策略模式

概述

策略模式(Strategy Pattern)是一种行为型设计模式,其核心思想是定义一系列可替换的算法(或行为),将每个算法封装成独立的策略类,使算法可独立于使用它的客户端而变化。客户端可根据实际需求动态选择不同的策略类,无需修改原有业务逻辑,实现了算法与客户端的解耦。

在策略模式中,客户端通过统一的接口调用不同的策略,策略类之间相互独立且可替换,既保证了代码的灵活性和扩展性,又让每个算法的职责边界清晰,符合“单一职责原则”和“开放-封闭原则”。

核心价值

核心要点 详细说明
算法解耦独立 每个算法(策略)被封装为独立类,与客户端和其他策略解耦,算法的修改、优化不会影响其他模块,降低代码耦合度
动态灵活切换 客户端可根据业务场景(如用户类型、配置参数等)动态选择不同策略,无需修改客户端代码,适配多样化需求
代码复用优化 相同类型的算法逻辑被封装在对应的策略类中,避免重复编码,提升代码复用性和可维护性
扩展成本极低 新增算法时,仅需实现策略接口创建新的策略类,无需修改原有客户端和已有策略,符合“开放-封闭原则”

适用场景

场景类型 具体说明
多规则动态选择场景 如用户类型差异化展示(男性/女性/VIP用户展示不同广告、商品类目)、支付方式选择(支付宝/微信/银行卡支付)等,需根据不同条件切换核心逻辑
算法多样化场景 如数据排序(冒泡排序/快速排序/归并排序)、数据校验规则(手机号校验/邮箱校验/身份证校验)等,存在多种可替换的算法逻辑
避免大量条件判断场景 当代码中出现过多 if-elseswitch 分支判断不同逻辑时,可通过策略模式封装分支逻辑,让代码更简洁易维护
算法需独立扩展场景 算法逻辑可能频繁新增或修改,且不希望影响原有业务流程,如营销活动规则(满减/折扣/优惠券)的动态调整

注意事项

注意点 具体说明
避免策略类过多 若策略类数量过多(如几十种),会增加系统复杂度和管理成本,可结合享元模式复用相同策略,或通过配置化优化
明确策略接口规范 所有策略类必须遵循统一的接口规范,确保客户端可通过相同方式调用,避免因接口不一致导致的调用异常
客户端需知晓策略差异 客户端需要了解不同策略的适用场景和差异,才能正确选择合适的策略,若策略逻辑复杂,可搭配工厂模式封装策略选择逻辑
避免策略类状态共享 策略类应尽量设计为无状态(或局部状态),避免多个客户端共享同一策略实例导致的状态污染,必要时使用原型模式创建独立实例

完整代码

以“用户类型差异化展示”为业务场景,构建策略模式实现:根据用户类型(男性/女性)展示对应的广告和商品类目数据。

<?php
declare(strict_types = 1);

/**
 * 策略接口:定义所有策略类的统一规范
 */
interface UserTypeStrategy
{
    /**
     * 展示广告
     * @return string
     */
    public function showAd(): string;

    /**
     * 展示商品类目
     * @return string
     */
    public function showCategory(): string;
}

/**
 * 具体策略1:男性用户策略(展示男性相关广告和类目)
 */
class MaleUserStrategy implements UserTypeStrategy
{
    public function showAd(): string
    {
        return "男性专属广告:运动装备、数码产品";
    }

    public function showCategory(): string
    {
        return "男性商品类目:户外用品、电子产品、男装";
    }
}

/**
 * 具体策略2:女性用户策略(展示女性相关广告和类目)
 */
class FemaleUserStrategy implements UserTypeStrategy
{
    public function showAd(): string
    {
        return "女性专属广告:美妆护肤、时尚服饰";
    }

    public function showCategory(): string
    {
        return "女性商品类目:化妆品、女装、配饰";
    }
}

/**
 * 上下文类:封装策略的使用逻辑,为客户端提供统一调用入口
 */
class HomeContext
{
    /**
     * 当前使用的策略实例
     * @var UserTypeStrategy
     */
    private UserTypeStrategy $strategy;

    /**
     * 设置策略(支持动态切换)
     * @param UserTypeStrategy $strategy
     */
    public function setStrategy(UserTypeStrategy $strategy): void
    {
        $this->strategy = $strategy;
    }

    /**
     * 首页数据展示(统一调用策略方法)
     * @return string
     */
    public function showHomeData(): string
    {
        $ad = $this->strategy->showAd();
        $category = $this->strategy->showCategory();
        return sprintf("【首页展示】<br/>广告:%s<br/>类目:%s", $ad, $category);
    }
}

// 测试代码
try {
    // 1. 创建上下文实例
    $homeContext = new HomeContext();

    // 2. 模拟客户端选择策略(根据用户类型参数)
    $userType = $_GET['user_type'] ?? 'male'; // 默认为男性用户

    // 3. 根据用户类型设置对应策略
    if ($userType === 'female') {
        $homeContext->setStrategy(new FemaleUserStrategy());
    } else {
        $homeContext->setStrategy(new MaleUserStrategy());
    }

    // 4. 调用统一接口展示数据
    echo $homeContext->showHomeData();
} catch (\Exception $e) {
    echo "首页展示异常:" . $e->getMessage();
}

核心结构拆解

策略模式的核心由“策略接口”“具体策略”和“上下文”三部分组成,结构清晰且职责明确:

结构组成 详细说明
策略接口(UserTypeStrategy) 定义所有具体策略必须实现的方法规范(如 showAd showCategory),确保客户端可通过统一接口调用不同策略
具体策略(MaleUserStrategy、FemaleUserStrategy 等) 实现策略接口,封装具体的算法逻辑(如男性/女性用户的广告和类目展示规则),策略间相互独立可替换
上下文(HomeContext) 作为客户端与策略类的中间层,封装策略的创建、切换和调用逻辑,为客户端提供简洁的调用入口,屏蔽策略类的具体实现细节

关键优化点

优化方向 详细说明
接口规范化 通过策略接口严格定义方法签名(含参数类型、返回值类型),确保所有具体策略的一致性,降低客户端调用风险
上下文封装 上下文类屏蔽了策略的具体实现和切换逻辑,客户端无需直接操作策略类,仅需通过 setStrategy 方法切换策略,简化调用流程
支持动态切换 上下文类的 setStrategy 方法允许在程序运行时动态更换策略,无需重启应用即可适配业务变化(如用户类型切换后实时更新展示内容)
无状态设计 具体策略类设计为无状态(不存储全局共享数据),确保多个客户端可安全复用同一策略实例,减少内存占用

常见错误规避

错误类型 问题描述及解决方案
策略接口不一致 问题:部分具体策略未完全实现接口方法,或方法签名与接口不一致,导致客户端调用报错。
解决:严格遵循策略接口规范,所有具体策略必须实现接口的全部方法,可通过IDE语法检查或单元测试验证
客户端直接依赖具体策略 问题:客户端跳过上下文直接创建和调用具体策略类,导致策略切换逻辑分散,耦合度升高。
解决:强制客户端通过上下文类调用策略,所有策略的创建和切换统一由上下文管理
策略逻辑耦合 问题:具体策略类中嵌入了与其他策略相关的逻辑,或依赖上下文的内部状态,导致策略无法独立复用。
解决:确保每个策略类仅关注自身算法逻辑,不依赖其他策略或上下文的内部细节,必要时通过参数传递所需数据
过度使用策略模式 问题:仅存在少量简单逻辑且无需扩展时,使用策略模式会增加代码复杂度(如仅两种固定逻辑且不会新增)。
解决:简单场景可直接使用条件判断,仅在逻辑多样化、需要频繁扩展或切换时使用策略模式

扩展场景示例(新增策略无需修改原有代码)

若业务新增“VIP用户专属展示策略”,仅需新增具体策略类并通过上下文切换,无需修改原有代码,符合“开放-封闭原则”:

// 新增具体策略:VIP用户策略
class VipUserStrategy implements UserTypeStrategy
{
    public function showAd(): string
    {
        return "VIP专属广告:高端定制服务、限量商品";
    }

    public function showCategory(): string
    {
        return "VIP商品类目:奢侈品、专属权益、优先购商品";
    }
}

// 客户端切换为VIP策略(无需修改原有上下文和其他策略)
$homeContext = new HomeContext();
$homeContext->setStrategy(new VipUserStrategy());
echo $homeContext->showHomeData();
// 输出结果:
// 【首页展示】
// 广告:VIP专属广告:高端定制服务、限量商品
// 类目:VIP商品类目:奢侈品、专属权益、优先购商品

实际应用场景

场景分类 具体说明
差异化营销 如电商平台根据用户等级(普通/会员/VIP)展示不同的优惠活动、推荐商品,每种用户等级对应一个策略类,动态切换营销逻辑
支付方式处理 订单支付时支持支付宝、微信、银行卡等多种支付方式,每种支付方式的签名、请求、回调逻辑封装为独立策略,客户端根据用户选择切换
数据校验规则 表单提交时需校验不同字段(手机号、邮箱、身份证),每种校验规则封装为策略,上下文根据字段类型选择对应策略执行校验
日志输出格式 系统日志支持JSON格式、文本格式、XML格式输出,每种格式的序列化逻辑封装为策略,可通过配置动态切换日志输出格式

策略模式与其他模式对比

对比维度 策略模式 职责链模式 观察者模式
核心目标 封装可替换算法,动态切换执行逻辑 请求在链中流转,找到对应处理者 主题状态变更,通知所有观察者
对象关系 上下文依赖策略接口(一对一) 链式结构,节点间单向依赖 一对多,主题依赖观察者接口
执行逻辑 客户端选择一种策略,单独执行 请求流转,单个或多个节点处理 主题通知,多个观察者同时处理
适用场景 算法动态切换、多规则选择 多节点分级处理、规则校验 事件触发多后续操作、状态同步