避免服务定位器反模式与传统应用程序不是为IOC设计的

ryb*_*ber 12 castle-windsor ninject ioc-container unity-container service-locator

我经常读到IOC中的服务定位器是一种反模式.

去年我们将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似乎是一种很好的方式,可以在我们有时间的地方慢慢地工作.

Rem*_*oor 12

因此,从我所阅读的内容中做到这一点的理想方法是在需要访问IOC时注入内核......这一切都很好,很好; 我们确实将SL的使用保持在最低限度.

不,将内核注入您的业务类并不是最好的方法.更好的方法是创建一个工厂,IFooFactory { IFoo Create(); }或者Func<IFoo>让这个创建一个新实例.

此接口的实现进入复合根,获取内核的实例并使用内核进行解析.这使您的类不受特定容器的影响,您可以使用不同的容器在另一个项目中重用它们.在Func的情况下,您可以使用以下模块:Ninject是否支持Func(自动生成的工厂)?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()}
Run Code Online (Sandbox Code Playgroud)

第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>());
Run Code Online (Sandbox Code Playgroud)

第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>());
Run Code Online (Sandbox Code Playgroud)

第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>());
Run Code Online (Sandbox Code Playgroud)

第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();
Run Code Online (Sandbox Code Playgroud)

步骤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();
Run Code Online (Sandbox Code Playgroud)

步骤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();
Run Code Online (Sandbox Code Playgroud)

您可能希望与重构一起切换到基于约定的容器配置.在这种情况下,我将为所有重构类添加一个属性来标记它们,并在完成所有重构后再次删除它.在约定中,您可以使用此属性来筛选所有重构的类.