我经常阅读Service Locators in IOC are an anti-pattern。
去年我们将IOC(特别是Ninject)介绍给了我们的工作应用程序。该应用程序是遗留的,它非常大,而且它是碎片化的。有很多方法可以创建类或类链。有些是由Web框架(自定义)创建的,有些是由nHibernate创建的。很多地方都散落在奇怪的地方。
我们如何处理不同的场景,而不是提出一些至少不是ServiceLocatorish的东西,而不是在不同的地方使用不同的内核(像singleton,HttpRequest和thread这样的范围很重要)。
编辑我会添加更多详细信息,以便了解我们当前的SL模式。
我们实际上并不想要多个内核。我们只想要一个(实际上因为SL我们只有一个静态的)。这是我们的设置:
1)我们在7-8个不同的项目/组件中有Ninject模块。当我们的应用程序(webapp)启动时,模块通过程序集扫描收集并加载到内核中并放置在服务定位器中。所以这一切都相当昂贵。
2)我们有一个自定义的UI框架,结构很开心。想想大约120个选项卡表单,每个表单构建1-10个标签页作为其生命周期的一部分。 SL战略性地用于5-6个地方,其中包括纯粹的分辨率或仅进行后实例化注入属性。
3)UI下的任何内容都不在那些顶级调用中,如果这些类想要使用IOC,他们需要提出自己的策略。有各种不同的小框架,每个框架都是他们自己的小世界。
因此,从我所阅读的内容中实现它的理想方法是在需要访问IOC时注入内核......这一切都很好而且很好;我们确实将SL的使用保持在最低限度。
但我从哪里获得这个内核?我不想在任何地方构建和注册新的。似乎我必须从静态上下文或工厂中获取它,因此我可以确保我正在使用的内核保持其他人正在使用的相同范围的对象,并且还要避免注册所有的模块。那时,无论那个东西看起来很像服务定位器吗?
请记住,这个应用程序是巨大的,紧密耦合。我们没有多次花几个月的时间来重构它。对于我们来说,SL似乎是一种很好的方式,可以让我们尽可能慢慢地在IOC中工作。
答案 0 :(得分:12)
所以从理论上做到这一点的理想方式 我读过的是注入内核 无论何时你需要访问IOC ......好吧 这一切都很好,很好;我们保持 将SL的使用降到最低。
不,将内核注入您的业务类并不是最好的方法。更好的方法是创建一个工厂,例如IFooFactory { IFoo Create(); }
或Func<IFoo>
让这个创建新实例。
此接口的实现进入复合根,获取内核的实例并使用内核完成解析。这使您的类不受特定容器的影响,您可以使用不同的容器在另一个项目中重用它们。在Func的情况下,您可以使用以下模块:Does Ninject support Func (auto generated factory)? Ninject 2.4将具有本机支持。
就重构而言,如果不知道应用程序的源代码,几乎不可能告诉你最好的方法。我可以给你一个可能有用的approch。
我想你想要将整个应用程序重构为适当的DI长期。我曾为一个相当大的项目(30-40人年)做过的事情是关于以下内容:
从复合根开始,然后解决对象树,并在另一个类之后更改一个类以使用正确的DI。一旦你到达所有的叶子,开始重构所有不依赖于其他服务的服务,并使用相同的方法工作到他们的叶子。之后,继续使用仅依赖于已经重构的服务的服务,并重复直到所有服务都被重构。所有这些步骤可以一个接一个地完成,以便代码不断得到改进,同时仍然可以添加新功能。同时,ServiceLocation是可以接受的,只要重点是尽快使其正确。
伪代码示例:
Foo{ ServiceLocator.Get<Service1>(), new Bar() }
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<Service3>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass()}
第1步 - 改变根(Foo)
Foo{ ctor(IService1, IBar) }
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<IService2>() }
Service2 { ServiceLocator.Get<IService3>() }
Service3 { new SomeClass()}
Bind<IBar>().To<Bar>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
第2步 - 更改root
的依赖关系Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ServiceLocator.Get<IService3>() }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass()}
Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
第3步 - 更改他们的依赖关系并继续,直到你在叶子
Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { new SomeClass() }
Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
Bind<IService3>().ToMethod(ctx => ServiceLocator.Get<IService3>());
步骤4 - 重构不依赖于其他服务的服务
Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ServiceLocator.Get<Service3>() }
Service3 { ctor(ISomeClass) }
Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>());
Bind<IService3>().To<Service3>().InSingletonScope();
步骤5 - 接下来重构依赖于仅将重构服务作为依赖的服务的那些。
Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ServiceLocator.Get<Service2>() }
Service2 { ctor(IService3) }
Service3 { ctor(ISomeClass) }
Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>());
Bind<IService2>().To<Service2>().InSingletonScope();
Bind<IService3>().To<Service3>().InSingletonScope();
步骤6 - 重复直到每个服务都被重构。
Foo{ ctor(IService1, IBar) }
Bar{ ctor(IService1, IService2, IBaz) }
Baz{ ctor(IService3) }
Service1 { ctor(IService2) }
Service2 { ctor(IService3) }
Service3 { ctor(ISomeClass) }
Bind<IBar>().To<Bar>();
Bind<IBaz>().To<Baz>();
Bind<ISomeClass>().To<SomeClass>();
Bind<IService1>().To<Service1>().InSingletonScope();
Bind<IService2>().To<Service2>().InSingletonScope();
Bind<IService3>().To<Service3>().InSingletonScope();
可能您希望与重构一起切换到基于约定的容器配置。在这种情况下,我将为所有重构类添加一个属性来标记它们,并在完成所有重构后再次删除它。在约定中,您可以使用此属性来过滤所有重构的类。