如何优雅处理策略模式场景下的依赖注入问题?
你当前用枚举属性匹配的思路本身没有问题,繁琐的根源是把策略查找逻辑散落在业务代码里、重复做遍历匹配,只要做两层封装就能把冗余逻辑全部消掉,以下是经过生产验证的通用实现,和具体DI框架无关:
优先用DI容器原生的键控注册能力
目前几乎所有主流DI容器(包括Prism配套的Unity、DryIoc,以及微软官方DI实现)都原生支持键控服务,不需要你自己注入全量策略集合再遍历。
注册时直接将策略标识作为键绑定到对应实现,不需要给IMakeCoffee接口额外加CoffeeType属性:// 注册阶段直接绑定键 container.RegisterKeyed<IMakeCoffee, MakeLatte>(CoffeeType.Latte); container.RegisterKeyed<IMakeCoffee, MakeEspresso>(CoffeeType.Espresso); container.RegisterKeyed<IMakeCoffee, MakeCappuccino>(CoffeeType.Cappuccino);单独封装一层策略工厂收敛查找逻辑,业务层不需要感知策略集合的存在:
public class CoffeeStrategyFactory { private readonly IKeyedServiceProvider _serviceProvider; public CoffeeStrategyFactory(IKeyedServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public IMakeCoffee Get(CoffeeType type) { // 找不到策略时的异常处理也可以统一放在这里 return _serviceProvider.GetRequiredKeyedService<IMakeCoffee>(type); } }业务类直接注入工厂即可,代码比原来简洁很多:
public class Barista { private readonly CoffeeStrategyFactory _strategyFactory; public Barista(CoffeeStrategyFactory strategyFactory) => _strategyFactory = strategyFactory; public Coffee MakeCoffee(CoffeeType coffeeType) { return _strategyFactory.Get(coffeeType).MakeCoffee(); } }策略数量多的场景,加特性标记+自动注册省掉重复注册代码
如果策略实现超过5个,手动写注册代码也会很繁琐,你可以自定义一个特性打在策略实现类上:[AttributeUsage(AttributeTargets.Class, Inherited = false)] public class MapCoffeeStrategyAttribute : Attribute { public CoffeeType BindType { get; } public MapCoffeeStrategyAttribute(CoffeeType bindType) => BindType = bindType; } // 给实现类打上标记 [MapCoffeeStrategy(CoffeeType.Latte)] public class MakeLatte : IMakeCoffee { /* 实现逻辑 */ }再写一个容器扩展方法,启动时扫描指定程序集,自动把所有带标记的策略实现按绑定的枚举值完成键控注册,全程不需要手动写每一条注册语句。
不建议的写法
不要直接在业务类里注入
IEnumerable<IMakeCoffee>然后手写FirstOrDefault匹配。这种写法把服务查找逻辑泄露到了业务层,后续修改匹配规则、加策略不存在的异常处理、调整键类型的时候,要改所有写了遍历逻辑的地方,维护成本会随策略数量上涨。把所有匹配、查找、异常处理逻辑收敛到工厂类,后续所有调整只需要改工厂一处代码。
如果你的项目用的DI容器暂时不支持键控注册,也可以在工厂类里注入IEnumerable<IMakeCoffee>,把你原来写的遍历匹配逻辑放到工厂内部实现,效果是一样的,本质都是把散落在业务层的逻辑做统一收敛。
内容的提问来源于stack exchange,提问作者RangerRick

