【问题标题】:Avoiding Service Locator Antipattern with legacy app not designed for IOC避免使用不是为 IOC 设计的遗留应用程序的服务定位器反模式
【发布时间】:2011-09-27 14:50:55
【问题描述】:

我经常读到Service Locators in IOC are an anti-pattern

去年,我们在工作中的应用程序中引入了 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 工作的好方法。

【问题讨论】:

  • 根据您给出的详细程度,我只能说去阅读@Mark Seeman 的最佳答案,然后回来提供一些相关细节,说明您为什么觉得需要多个内核和(除了重构期间的中间阶段),您将拥有各种各样的组合根。现在这个问题太开放了,任何人都无法回答,并觉得他们正在添加上述帖子中尚未涵盖的有价值的东西

标签: castle-windsor unity-container ioc-container ninject service-locator


【解决方案1】:

所以理想的方法是从什么 我读过正在注入内核 每当您需要访问 IOC 时...嗯 这一切都很好;我们确实保留 将 SL 的使用降到最低。

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

此接口的实现进入复合根,获取内核实例并使用内核进行解析。这使您的类不受特定容器的影响,您可以在另一个项目中使用不同的容器重用它们。如果是 Func,您可以使用以下模块:Does Ninject support Func (auto generated factory)? 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();

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

【讨论】:

  • 谢谢,实际上这离我们正在做的事情不远了。我们不久前更改了我们的服务定位器以在内部使用一个工厂(它有一个接口),该工厂又包含内核,这在某些测试场景中有所帮助,因为我们可以交换不同的模拟。直接使用该工厂而不是静态服务定位器调用只需一点点。
猜你喜欢
  • 2014-05-26
  • 2021-12-17
  • 2021-01-15
  • 1970-01-01
  • 1970-01-01
  • 2016-08-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多