策略类必须实现统一接口,否则无法动态替换;需定义明确接口(如DiscountStrategy)并强制所有策略类实现,Context通过依赖注入接收策略实例,避免硬编码创建,确保开闭原则。

策略类必须实现统一接口,否则无法动态替换
策略模式的核心是把不同算法封装成独立类,靠共同接口解耦调用方。如果每个策略类方法名不一致(比如有的叫 calculate(),有的叫 doLogic()),或者参数数量/类型不统一,Context 就没法安全调用——运行时容易报 Fatal error: Uncaught Error: Call to undefined method。
实操建议:
- 定义一个明确的接口,例如
interface DiscountStrategy,强制声明apply(float $price): float - 所有策略类(
StudentDiscount、VipDiscount、SeasonalDiscount)都implements DiscountStrategy - 避免在策略里直接 echo 或 header 重定向——策略只负责计算或判断,副作用交给上层处理
Context 类别写死 new 实例,就失去策略模式意义
常见错误是把策略创建写死在 Context 构造函数里:$this->strategy = new VipDiscount();。这样虽然消了 if,但新增策略还得改 Context 代码,违背开闭原则。
实操建议:
-
Context构造函数接收策略实例,类型约束为接口:public function __construct(DiscountStrategy $strategy) - 工厂或依赖注入容器负责根据条件选具体策略类,例如从配置读
discount.type再 new 对应类 - 别在
Context里做 switch 判断——那只是把 if 换成 switch,没解决问题本质
PHP 8+ 可用 match 表达式简化策略选择,但不能替代策略对象
有人用 match($type) { 'vip' => new VipDiscount(), 'student' => new StudentDiscount() } 替代 if,看起来干净,但它只是策略创建逻辑的语法糖,不是策略模式本身。一旦策略逻辑变复杂(比如要读数据库、调 API、带状态),纯 match 就撑不住。
实操建议:
- match / switch 仅用于“选哪个策略类”,不用于“实现策略逻辑”
- 策略类内部可依赖其他服务(如
UserService、CacheInterface),通过构造函数注入 - 注意 PHP 8.1+ 支持枚举,可用
enum DiscountType: string替代字符串魔法值,减少拼写错误
策略间共享状态容易引发隐性 bug
多个策略共用同一个 static 属性,或通过全局变量传参,会导致行为不可预测——比如前一个请求设了 self::$debug = true,后一个请求意外开启调试日志。
实操建议:
- 策略类保持无状态(stateless),所有输入通过方法参数传入
- 真需要上下文数据(如用户等级、地区),由
Context统一收集后作为参数传给$strategy->apply($price, $contextData) - 避免在策略中使用
$_SESSION或$_SERVER等超全局变量——它们不属于当前策略的契约范围
策略模式真正起效的地方,不在结构多漂亮,而在你下次加一种新折扣时,不用碰老代码、不测全量、不担心影响其他分支逻辑。最容易被忽略的是:策略类的生命周期管理——它该是单例?每次 new?还是按请求 scope 创建?这直接决定缓存、连接复用和内存占用。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











