.NET 8 新特性 Keyed Services 的正确打开方式
内容提要
.NET 8 引入了键控服务(Keyed Services)特性,允许注册同一接口的多个实现,并在运行时根据名称选择合适的实现。使用键控服务可以动态选择服务实现,适合实现策略模式。建议定义特定接口继承自通用接口,以清晰表达程序意图。键控服务在编译时不固定服务实现,可根据场景或条件选择不同实现。
延伸解读
键控服务并非万能:何时该用特定接口
文章指出,如果设计阶段已明确使用哪个实现,直接依赖通用接口并通过键控服务解析反而是一种代码异味。此时更推荐定义继承自通用接口的特定接口(如IBigCache),让依赖关系在编译期就清晰表达。键控服务的真正优势在于运行时动态选择,而非替代明确的静态依赖。
策略模式中的键控服务:消除条件分支
在电商发送订单通知的例子中,传统做法用if-else根据用户SendType选择发送器,新增方式需同时修改注册和使用代码。改用键控服务后,以SendType为键在运行时解析ISender,新增发送方式只需注册新的键控服务,无需改动使用代码,提升了可扩展性。
使用键控服务时需注意的依赖注入方式
示例中通过注入IServiceProvider并调用GetRequiredKeyedService来获取服务,这虽然灵活,但也意味着服务解析从构造函数转移到了方法内部。读者需留意这种模式可能隐藏依赖关系,建议仅在确实需要运行时动态选择的场景中使用,避免滥用。
Q&A
.NET 8 的键控服务是什么?它解决了什么问题?
键控服务是 .NET 8 引入的新特性,允许使用不同名称注册同一接口的多个实现,并在运行时根据名称选择合适的实现。它解决了需要动态选择服务实现的场景,提高了代码的灵活性和可扩展性。
键控服务适合在什么场景下使用?
键控服务适合在运行时需要动态选择服务实现的场景,特别是实现策略模式时。例如,根据用户设置或条件选择不同的发送方式(如邮件、短信)。
为什么作者认为官方示例中的键控服务用法不够合理?
作者认为,如果在设计阶段已经明确需要使用哪个实现,就不应该依赖注入通用接口再通过键控服务选择。更合理的做法是定义继承自通用接口的特定接口(如 IBigCache、ISmallCache),直接注入特定接口,这样能更清晰地表达程序意图。
如何使用键控服务实现策略模式?请举例说明。
首先注册键控服务,例如:builder.Services.AddKeyedTransient<ISender, EmailSender>("Email"); 然后在使用时通过 IServiceProvider 的 GetRequiredKeyedService 方法,根据键(如 user.SendType)获取对应的实现并调用。这样新增发送方式时只需注册新的键控服务,无需修改使用代码。
使用键控服务实现策略模式有哪些好处?
使用键控服务可以提高代码的灵活性和可扩展性。当增加新的服务实现时,只需注册新的键控服务,无需修改使用代码,符合开闭原则。
键控服务与直接注入特定接口相比,主要区别是什么?
键控服务允许在运行时根据名称动态选择实现,而直接注入特定接口在编译时就固定了实现。键控服务更灵活,适合需要根据条件动态切换的场景;而直接注入特定接口更清晰,适合已知固定实现的场景。