概述
策略模式(Strategy Pattern)是一种行为型设计模式,其核心思想是定义一系列可替换的算法(或行为),将每个算法封装成独立的策略类,使算法可独立于使用它的客户端而变化。客户端可根据实际需求动态选择不同的策略类,无需修改原有业务逻辑,实现了算法与客户端的解耦。
在策略模式中,客户端通过统一的接口调用不同的策略,策略类之间相互独立且可替换,既保证了代码的灵活性和扩展性,又让每个算法的职责边界清晰,符合“单一职责原则”和“开放-封闭原则”。
核心价值
| 核心要点 | 详细说明 |
|---|---|
| 算法解耦独立 | 每个算法(策略)被封装为独立类,与客户端和其他策略解耦,算法的修改、优化不会影响其他模块,降低代码耦合度 |
| 动态灵活切换 | 客户端可根据业务场景(如用户类型、配置参数等)动态选择不同策略,无需修改客户端代码,适配多样化需求 |
| 代码复用优化 | 相同类型的算法逻辑被封装在对应的策略类中,避免重复编码,提升代码复用性和可维护性 |
| 扩展成本极低 | 新增算法时,仅需实现策略接口创建新的策略类,无需修改原有客户端和已有策略,符合“开放-封闭原则” |
适用场景
| 场景类型 | 具体说明 |
|---|---|
| 多规则动态选择场景 | 如用户类型差异化展示(男性/女性/VIP用户展示不同广告、商品类目)、支付方式选择(支付宝/微信/银行卡支付)等,需根据不同条件切换核心逻辑 |
| 算法多样化场景 | 如数据排序(冒泡排序/快速排序/归并排序)、数据校验规则(手机号校验/邮箱校验/身份证校验)等,存在多种可替换的算法逻辑 |
| 避免大量条件判断场景 | 当代码中出现过多 if-else 或 switch 分支判断不同逻辑时,可通过策略模式封装分支逻辑,让代码更简洁易维护 |
| 算法需独立扩展场景 | 算法逻辑可能频繁新增或修改,且不希望影响原有业务流程,如营销活动规则(满减/折扣/优惠券)的动态调整 |
注意事项
| 注意点 | 具体说明 |
|---|---|
| 避免策略类过多 | 若策略类数量过多(如几十种),会增加系统复杂度和管理成本,可结合享元模式复用相同策略,或通过配置化优化 |
| 明确策略接口规范 | 所有策略类必须遵循统一的接口规范,确保客户端可通过相同方式调用,避免因接口不一致导致的调用异常 |
| 客户端需知晓策略差异 | 客户端需要了解不同策略的适用场景和差异,才能正确选择合适的策略,若策略逻辑复杂,可搭配工厂模式封装策略选择逻辑 |
| 避免策略类状态共享 | 策略类应尽量设计为无状态(或局部状态),避免多个客户端共享同一策略实例导致的状态污染,必要时使用原型模式创建独立实例 |
完整代码
以“用户类型差异化展示”为业务场景,构建策略模式实现:根据用户类型(男性/女性)展示对应的广告和商品类目数据。
<?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格式输出,每种格式的序列化逻辑封装为策略,可通过配置动态切换日志输出格式 |
策略模式与其他模式对比
| 对比维度 | 策略模式 | 职责链模式 | 观察者模式 |
|---|---|---|---|
| 核心目标 | 封装可替换算法,动态切换执行逻辑 | 请求在链中流转,找到对应处理者 | 主题状态变更,通知所有观察者 |
| 对象关系 | 上下文依赖策略接口(一对一) | 链式结构,节点间单向依赖 | 一对多,主题依赖观察者接口 |
| 执行逻辑 | 客户端选择一种策略,单独执行 | 请求流转,单个或多个节点处理 | 主题通知,多个观察者同时处理 |
| 适用场景 | 算法动态切换、多规则选择 | 多节点分级处理、规则校验 | 事件触发多后续操作、状态同步 |


