我已经阅读了几篇关于依赖注入的Mark Seeman文章,特别是避免服务定位器模式的原因:
Service Locator背后存在问题的基本思想是它可能在运行时失败:
public class OrderProcessor : IOrderProcessor
{
public void Process(Order order)
{
var validator = Locator.Resolve<IOrderValidator>();
if (validator.Validate(order))
{
var shipper = Locator.Resolve<IOrderShipper>();
shipper.Ship(order);
}
}
}
var orderProcessor = new OrderProcessor();
// following line fails at compile time if you
// forget to register all necessary services - and
// you don't have a way of knowing which services it needs
orderProcessor.Process(someOrder);
但是这意味着组合根不仅必须在启动时解析所有依赖关系,而且实际上实例化整个对象图,否则我们仍然不会知道所有必需的依赖关系已经注册:
private static void Main(string[] args)
{
var container = new WindsorContainer();
container.Kernel.Resolver.AddSubResolver(
new CollectionResolver(container.Kernel));
// Register
container.Register(
Component.For<IParser>()
.ImplementedBy<WineInformationParser>(),
Component.For<IParser>()
.ImplementedBy<HelpParser>(),
Component.For<IParseService>()
.ImplementedBy<CoalescingParserSelector>(),
Component.For<IWineRepository>()
.ImplementedBy<SqlWineRepository>(),
Component.For<IMessageWriter>()
.ImplementedBy<ConsoleMessageWriter>());
// Everything must be resolved AND instantiated here
var ps = container.Resolve<IParseService>();
ps.Parse(args).CreateCommand().Execute();
// Release
container.Release(ps);
container.Dispose();
}
在现实世界的应用程序中,这有多可行?这是否真的意味着你不应该在构造函数之外的任何地方实例化任何东西?
(附加信息)
假设您有一项服务,该服务应该处理来自某种类型的多个测量设备(不同的连接类型,协议或相同协议的不同版本)的传入连接。无论何时获得新连接,服务都应该从输入端口,通过fifo缓冲区构建一个“管道”,到特定于该设备类型的许多解析器,以多个消费者为各种解析消息结束。
事先编写这些对象图是应用程序启动时似乎无法实现的。即使它可以被延迟,我仍然看不出如何能够获得早期(-er)指示对象图构建失败。
这似乎是main problem with service locators,我看不出如何避免它:
简而言之,Service Locator的问题在于它隐藏了一个类的依赖项,导致运行时错误而不是编译时错误,以及使代码更难以维护,因为它不清楚你何时会引入一个突破性的变化。
答案 0 :(得分:4)
但这意味着Composition Root不仅必须在启动时解析所有依赖项,还要实际实例化整个对象图
如果您应用Pure DI(即应用依赖注入模式,但没有DI容器),您将获得开箱即用的编译时支持。使用DI容器,您必须在运行时进行这些检查,但这并不意味着您必须在启动期间执行此操作,尽管我认为这是首选。因此,如果在启动时检查容器的配置并不会导致性能问题,则应该执行此操作。否则,您可以将此验证步骤移至单元测试。
在现实世界的应用程序中,这有多可行?
这完全可行。我构建了大型应用程序,通常在容器中注册了数百到超过一千个服务,我总是验证(和diagnose)我的容器配置,这可以防止许多常见的配置错误,这些错误非常容易制作,非常容易难以追查。
这是否真的意味着你不应该在构造函数之外的任何地方实例化任何东西?
您的作品根负责创建您的服务;这并不意味着在启动期间应该创建所有服务,因为您可以延迟创建对象图的部分直到运行时。但是,我的首选工作方式是使所有注册服务单例(应用程序期间的一个实例)。这使得在应用程序启动期间创建所有服务变得非常容易(且便宜),并迫使您进入更严格的模型,其中SOLID违规和其他DI错误实践更快地弹出。