策略模式将排序逻辑封装为独立可替换对象,解决硬编码导致的开闭原则违背、测试困难与复用性差问题;通过SortStrategy接口及具体实现解耦“排什么”与“怎么排”,上下文ProductSorter仅负责接收并执行策略,避免功能膨胀。

直接用策略模式封装排序逻辑,比在控制器里堆 usort + 匿名函数或写一堆 if-else 分支更可控,也更容易测试和替换。
为什么不能把排序逻辑硬编码在业务方法里?
常见错误现象是:一个 getProducts() 方法里塞了 4 种排序分支,靠 $sortType 参数判断走哪个 usort();一旦加第五种(比如“按库存预警值倒序”),就得改这个方法,违反开闭原则。更糟的是,这些排序逻辑没法单独单元测试,也没法复用到用户列表、订单列表等其他地方。
根本问题在于:排序行为和数据获取耦合太紧。策略模式把“怎么排”抽成独立对象,让“排什么”和“怎么排”解耦。
如何定义排序策略接口与具体实现?
接口要足够通用,但又不能太宽泛。推荐用 callable 或统一输入输出结构:
立即学习“PHP免费学习笔记(深入)”;
interface SortStrategy
{
public function sort(array $items): array;
}
具体实现只关注排序逻辑,不碰数据库或缓存:
-
PriceAscSort:用array_multisort(array_column($items, 'price'), SORT_ASC, $items) -
NameLengthDescSort:用usort($items, fn($a, $b) => strlen($b['name']) strlen($a['name'])) -
CustomPrioritySort:接受额外参数(如$priorityMap = ['premium' => 1, 'basic' => 2]),内部按映射值排序
注意:所有策略类必须返回 array,且不修改原数组(避免副作用)。如果原始数据来自 Doctrine ORM 或 Eloquent Collection,先转成数组再传入策略,别让策略直接操作 Query Builder。
上下文类怎么设计才不容易踩坑?
上下文不是万能胶水,它只做两件事:接收策略、执行排序。不要在里面加过滤、分页或格式化。
class ProductSorter
{
private SortStrategy $strategy;
public function __construct(SortStrategy $strategy)
{
$this->strategy = $strategy;
}
public function sort(array $products): array
{
return $this->strategy->sort($products);
}
}
关键点:
- 构造时强制依赖策略,不提供无参构造或默认策略(避免隐式行为)
- 不暴露
setStrategy()—— 运行时切换策略看似灵活,但容易导致状态混乱;改用新实例更安全 - 如果不同控制器需要不同排序,就注入不同
ProductSorter实例(例如通过 DI 容器绑定ProductSorter::class到不同策略)
性能影响:策略本身不引入额外开销,但要注意 usort 在大数据量时可能触发 PHP 内存限制;必要时在策略内加 array_slice() 预处理,或改用数据库原生排序(策略退化为 SQL 排序字段生成器)。
依赖注入时怎么避免策略爆炸?
当策略超过 5 个,手动 new 每个策略会很累,也难维护。推荐两种方式:
- 用容器自动解析:在 Laravel 中绑定
SortStrategy::class的不同实现到不同 key,如app()->bind('sort.price_asc', PriceAscSort::class),然后通过app('sort.price_asc')获取 - 用工厂类集中管理:
SortStrategyFactory::make('price_desc')返回对应实例,内部用match或配置表驱动,避免硬编码 switch
容易被忽略的点:策略类之间不应有继承关系,也不该共享父类逻辑(比如公共比较函数)——那说明抽象粒度错了,应该提取成独立工具函数,而不是塞进策略基类里。每个策略必须是正交、可独立替换的单元。



















