策略设计模式场景下如何避免使用Service Locator反模式
解决方案
你可以通过专属策略工厂+DI容器的服务自动收集能力解决这个问题,完全避开服务定位器反模式,同时避免Resolver依赖列表过长的问题,核心思路是用只负责提供目标策略族的受限工厂替代通用服务定位器,所有策略的实例化都交给DI容器处理。
方案1:使用类型受限的专属策略工厂
通用服务定位器的问题在于它可以返回任意类型的服务,隐藏依赖、类型不安全。你可以实现一个只允许返回StrategyInterface实现类的专属工厂,它的所有可用策略创建逻辑都由DI容器预先绑定:
// 专属策略工厂,仅负责提供StrategyInterface的实现,不属于通用服务定位器 class StrategyFactory { // 构造函数接收的策略创建回调由DI容器预先绑定,每个回调都能自动注入对应策略的依赖 private array $strategyCreators; public function __construct(array $strategyCreators) { // 提前做类型校验,确保所有回调返回的都是合法策略,保证类型安全 foreach ($strategyCreators as $creator) { if (!is_callable($creator) || !is_a($creator(), StrategyInterface::class, true)) { throw new \InvalidArgumentException('非法的策略创建回调'); } } $this->strategyCreators = $strategyCreators; } public function get(string $strategyClass): StrategyInterface { if (!isset($this->strategyCreators[$strategyClass])) { throw new \RuntimeException('请求的策略不存在'); } return $this->strategyCreators[$strategyClass](); } } // 改造后的StrategyResolver class StrategyResolver { private StrategyFactory $strategyFactory; // 仅依赖1个专属工厂,依赖列表始终简洁 public function __construct(StrategyFactory $strategyFactory) { $this->strategyFactory = $strategyFactory; } public function resolve($data): StrategyInterface { if (xxx) { return $this->strategyFactory->get(A::class); } elseif (yyy) { return $this->strategyFactory->get(B::class); } return $this->strategyFactory->get(C::class); } }
方案2:配合框架的服务标记自动收集
主流DI框架(Laravel/Symfony/PHP-DI等)基本都支持给服务打标签、按标签批量注入的能力,你不需要手动维护策略列表,加新策略只要给类打对应标签即可,完全不用修改Resolver的代码:
以Laravel为例,仅需要在服务提供者中做一次配置:
// 给所有策略类打同一个标签 $this->app->tag([A::class, B::class, C::class], 'app.strategy'); // 将所有打了对应标签的服务批量注入到StrategyResolver $this->app->when(StrategyResolver::class) ->needs('$strategyMap') ->giveTagged('app.strategy');
后续新增策略只需要把新的策略类加到tag方法的数组中即可,不需要修改Resolver的任何逻辑。
方案优势
- 完全符合DI规范:所有策略的依赖都由DI容器自动注入,保留了DI可测试、自动解析的全部优势
- 无冗长依赖:Resolver只需要依赖1个工厂或者策略集合,哪怕有数十个策略也不会增加依赖数量
- 类型安全:和通用服务定位器不同,你无法从专属工厂中获取到非
StrategyInterface类型的服务,依赖完全显式可控 - 懒加载友好:可以通过DI容器的懒加载代理或者回调创建的方式,只实例化当前请求用到的策略,避免不必要的资源消耗
- 扩展性强:新增策略只需要加实现类、配置DI规则,不需要修改Resolver的现有逻辑,符合开闭原则
内容的提问来源于stack exchange,提问作者colorgreen
相关产品推荐
相关产品推荐

