问题描述
我经常阅读 IOC中的服务定位符是反模式一个>.
去年,我们在工作中引入了IOC(专门用于Ninject).该应用程序是旧版,非常大,而且分散.创建一个类或一系列类的方法有很多.有些是由Web框架(自定义)创建的,有些是由nHibernate创建的.很多东西都散布在怪异的地方.
Last year we introduced IOC (Ninject specifically) to our application at work. The app is legacy, it's very big and it's fragmented. There are lots of ways a class, or a chain of classes can get created. Some are created by the web framework (which is custom), some are created by nHibernate. Lots are just scattered around in weird places.
我们将如何处理不同的情况,而不会提出至少ServiceLocatorish的东西,并且最终不会在不同的地方使用不同的内核(单例,HttpRequest和线程等范围很重要).
How would we handle the different scenarios and not come up with something thats not at least ServiceLocatorish and not end up with different kernels in different places (scopes like singleton, HttpRequest and thread are important).
编辑,我将为导致我们采用当前SL模式的内容添加更多细节.
Edit I'll add a little more detail to what led us to our current SL pattern.
我们实际上并不想要多个内核.我们只想要一个(而实际上由于SL,我们只有一个静态的).这是我们的设置:
We actually don't want multiple kernels. We just want one (and indeed because of the SL we only have the one static one). Here is our setup:
1)我们在7-8个不同的项目/程序集中有Ninject模块.当我们的应用程序(一个Web应用程序)启动时,模块将通过程序集扫描收集并加载到内核中,然后放置在服务定位器中.因此,所有这些都相当昂贵.
1) We have Ninject Modules in 7-8 different projects/assemblies. When our app (a webapp) starts the modules are gathered via assembly scanning and loaded into a kernel and placed in the Service Locator. So all that is rather expensive.
2)我们有一个自定义的UI框架,构建起来很愉快.考虑大约120个选项卡式表单,每个表单构成1-10个选项卡页作为其生命周期的一部分. SL具有策略性地用于5-6个位置,这些位置可作为纯分辨率或仅对属性进行实例化后注入.
2) We have a custom UI framework that's construction happy. Think of around 120 tabbed forms which each construct 1-10 tab pages as part of their lifecycle. The SL is strategically used in 5-6 places which cover all of this either as pure resolution or doing post instantiation injection of properties only.
3)这些顶级调用未涵盖UI之下的任何内容,如果这些类想使用IOC,则需要提出自己的策略.有各种不同的小框架,每个小框架都是它们自己的小世界.
3) Anything under the UI is not covered by those top level calls and if those classes want to use IOC they need to come up with their own strategy. There are various different little frameworks which are each their own little worlds.
因此,根据我所阅读的内容,实现此目标的理想方法是在每次需要访问IOC时都注入一个内核……这一切都很好.我们会尽量减少对SL的使用.
So the ideal way of doing it from what I have read is injecting a kernel whenever you need to access IOC...well that's all fine and good; we do keep the use of the SL to a minimum.
但是我从哪里得到这个内核?我不想到处构造和注册一个新的.看来我必须将其从静态上下文或工厂中删除,以便可以确保所使用的内核与其他所有人使用的作用域对象保持一致,并且还避免了注册所有内核对象的开销.模块.在那个时候,无论那件事看起来很像服务定位器,对吗?
But where am I getting this kernel from? I don't want to construct and register up a new one everywhere. It seems like I would have to get it out of static context or factory so I can ensure that the kernel I'm using is holding on to the same scoped objects that everyone else is using and also to avoid the expense of registering all of the modules. At that point, whatever that thing is seems a lot like a Service Locator right?
还请记住,此应用程序庞大且紧密耦合.我们没有花费一整月的时间进行重构的奢侈行为.对于我们来说,SL似乎是一种很好的方式,可以让我们在有空的时候在可能的地方缓慢地工作.
Keep in mind also that this app is HUGE and tightly coupled. We don't have the luxury of spending a few months in one go of refactoring it. The SL seemed like a good way for us to slowly work IOC in where we could as we had time.
推荐答案
不,将内核注入您的业务类并不是最佳的方法.更好的方法是创建一个工厂,例如IFooFactory { IFoo Create(); }
或Func<IFoo>
并让其创建新实例.
No, injecting the kernel into your business classes is not the best way to go. The better way is to create a factory e.g. IFooFactory { IFoo Create(); }
or Func<IFoo>
and let this one create the new instance.
此接口的实现进入复合根,获取内核的实例并使用内核进行解析.这样可以使您的类脱离特定的容器,并且可以使用其他容器在另一个项目中重用它们.如果是Func,则可以使用以下模块:是否提供Ninject支持Func(自动生成的工厂)? Ninject 2.4将对此提供本机支持.
The implementation of this interface goes into the composite root, gets an instance of the kernel and does the resolve using the kernel. This keeps your classes free of a specific container and you can reuse them in another project using a different container. In case of Func you can use the following module: Does Ninject support Func (auto generated factory)? Ninject 2.4 will have native support for this.
就重构而言,很难在不知道应用程序源代码的情况下告诉您什么是最好的方法.我可以给您一个可能可行的方法.
As far as the refactoring goes, it is hardly possible to tell you what's the best way to go without knowing the source code of your application. I can just give you an approch that probably can work.
我想您希望长期将整个应用程序重构为适当的DI.我曾经为一个相当大的项目(30-40人年)做过以下事情:
I suppose you want to refactor the whole application to proper DI in long term. What I did once for a quite large project (30-40 man-years) was about the following:
从复合根开始,对对象树进行处理,然后依次更改一个类以使用正确的DI.到达所有叶子后,便开始重构所有不依赖于其他服务的服务,并使用相同的方法对其叶子进行处理.之后,继续仅依赖于已经重构的服务的服务,并重复进行直到所有服务都重构为止.所有这些步骤可以一个接一个地完成,因此代码可以不断得到改进,同时仍可以同时添加新功能.同时,只要能够尽快使ServiceLocation正确,ServiceLocation是可以接受的.
Start at the composite root(s) and work down the object tree and change one class after the other to use proper DI. Once you reached all leafs start to refactor all the services that do not depend on other services and work to their leafs using the same approach. Afterwards, continue with the services that depend only on services that have already been refactored and repeat until all services are refactored. All these steps can be done one after the other so that the code continously gets improved while new features can still be added at the same time. In the mean time ServiceLocation is acceptable, as long as the focus is to get it right as soon as possible.
伪代码示例:
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)
Step 1 - Change Root (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步-更改根的依赖性
Step 2 - Change dependencies of 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步-更改其依存关系并继续操作,直到您到了叶子为止
Step 3 - Change their dependencies and continue until you are at the leafs
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步-重构不依赖其他服务的服务
Step 4 - Refactor the services that do not depend on other ones
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步-接下来,重构那些依赖那些仅将服务重构为依赖项的服务.
Step 5 - Next refactor those that depend on services that have only refactored services as dependency.
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-重复进行,直到重构每个服务为止.
Step 6 - Repeat until every service is refactored.
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();
可能您想与重构一起切换到基于约定的容器配置.在这种情况下,我将向所有重构的类添加一个属性以对其进行标记,并在完成所有重构后再次将其删除.在约定中,可以使用此属性过滤所有重构的类.
Probably you want to switch to a convention based container configuration together with the refactoring. In this case I'd add an attribute to all refactored classes to mark them and remove it again after all the refactoring is done. In the conventions you can use this attribute to filter for all the refactored classes.
这篇关于避免使用不是专为IOC设计的旧版应用程序的Service Locator Antipattern的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!