12

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

去年,我们在工作中的应用程序中引入了 IOC(特别是 Ninject)。该应用程序是遗留的,它非常大而且分散。有很多方法可以创建一个类或一个类链。有些是由 web 框架创建的(这是自定义的),有些是由 nHibernate 创建的。很多只是散落在奇怪的地方。

我们将如何处理不同的场景,而不是想出至少不是 ServiceLocatorish 的东西,并且最终不会在不同的地方使用不同的内核(单例、HttpRequest 和线程等范围很重要)。

编辑我将添加更多细节来说明是什么导致了我们目前的 SL 模式。

我们实际上不想要多个内核。我们只想要一个(实际上由于 SL,我们只有一个静态的)。这是我们的设置:

1) 我们在 7-8 个不同的项目/程序集中有 Ninject 模块。当我们的应用程序(webapp)启动时,模块通过程序集扫描收集并加载到内核中并放置在服务定位器中。所以这一切都是相当昂贵的。

2) 我们有一个定制的 UI 框架,构建起来很开心。想想大约 120 个选项卡式表单,每个表单都构建 1-10 个选项卡页作为其生命周期的一部分。SL 战略性地用于 5-6 个地方,这些地方涵盖了所有这些,或者作为纯分辨率,或者只进行属性的实例化后注入。

3) UI 下的任何内容都没有被那些顶级调用覆盖,如果这些类想要使用 IOC,他们需要提出自己的策略。有各种不同的小框架,每一个都是他们自己的小世界。

因此,从我所读到的内容中,理想的方法是在您需要访问 IOC 时注入内核......好吧,这一切都很好;我们确实将 SL 的使用降至最低。

但是我从哪里得到这个内核呢?我不想到处构建和注册一个新的。似乎我必须将它从静态上下文或工厂中取出,这样我才能确保我正在使用的内核保持与其他人正在使用的相同范围对象,同时也避免注册所有的费用模块。在那一点上,无论那个东西是什么看起来很像一个服务定位器,对吧?

还要记住,这个应用程序非常庞大且紧密耦合。我们没有奢侈地花几个月的时间来一次重构它。SL 似乎是我们在有时间的情况下慢慢地在 IOC 工作的好方法。

4

1 回答 1

12

因此,从我所读到的内容中,理想的方法是在您需要访问 IOC 时注入内核......好吧,这一切都很好;我们确实将 SL 的使用降至最低。

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

该接口的实现进入复合根,获取内核实例并使用内核进行解析。这使您的类不受特定容器的影响,您可以在另一个项目中使用不同的容器重用它们。如果是 Func,您可以使用以下模块:Ninject 是否支持 Func(自动生成工厂)?Ninject 2.4 将对此提供本机支持。


就重构而言,在不了解应用程序源代码的情况下,几乎不可能告诉您最好的方法是什么。我可以给你一个可能可行的方法。

我想您想长期将整个应用程序重构为适当的 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 步 - 更改根的依赖项

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();

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

于 2011-07-05T17:21:43.557 回答