如果单身人士不好,那么为什么服务容器好?

我们都知道单身人士是多么的糟糕 ,因为他们隐藏了依赖和其他原因 。

但是在一个框架中,可能有许多对象只需要实例化一次,并从任何地方调用(logging器,db等)。

为了解决这个问题,我被告知要使用一个所谓的“对象pipe理器”(或像symfony这样的服务容器 ),在内部存储对服务(logging器等)的每一个引用。

但是,为什么服务提供商不如纯粹的单身人士?

服务提供者也隐藏了依赖关系,他们只是把第一个创build的东西包装起来。 所以我真的很难理解为什么我们应该使用服务提供者而不是单身人士。

PS。 我知道,为了不隐藏依赖关系,我应该使用DI(如Misko所述)

我想补充一下:现在单身人士并不是那么邪恶,PHPUnit的创造者在这里解释了这一点:

  • http://sebastian-bergmann.de/archives/882-Testing-Code-That-Uses-Singletons.html

DI + Singleton解决了这个问题:

<?php class Client { public function doSomething(Singleton $singleton = NULL){ if ($singleton === NULL) { $singleton = Singleton::getInstance(); } // ... } } ?> 

即使这不能解决所有的问题,这也是相当聪明的。

除DI和服务容器之外,还有什么好的可接受的解决scheme来访问这个帮助对象?

服务定位器只是两个邪恶中的一小部分。 这些“较小”的东西可以归结为这四种差异( 至less现在我想不出任何其他的东西 ):

单一责任原则

服务容器并不违反像Singleton一样的单一责任原则。 单身人士将对象创build和业务逻辑混合在一起,而服务容器则严格负责pipe理应用程序的对象生命周期。 在这方面服务容器比较好。

耦合

由于静态的方法调用,单身人士通常会被硬编码到您的应用程序中,这会导致您的代码中紧密耦合和难以模拟的依赖关系 。 SL另一方面只是一个类,它可以注入。 所以,虽然所有的分类都依赖于它,但至less它是一个松散耦合的依赖关系。 所以,除非你将ServiceLocator作为一个单例本身来实现,否则这样会更好,也更容易testing。

但是,所有使用ServiceLocator的类现在都依赖于ServiceLocator,它也是一种耦合forms。 这可以通过使用ServiceLocator接口来缓解,因此您不必绑定到具体的ServiceLocator实现,但是您的类将取决于某种Locator的存在,而不使用ServiceLocator则会显着增加重用。

隐藏的依赖

但是隐藏依赖关系的问题非常多。 当你只是将定位器注入你的消费类,你不会知道任何依赖。 但是与Singleton相比,SL通常会实例化所有幕后所需的依赖关系。 所以,当你获取一个服务时,你不会像CreditCard示例中的Misko Hevery那样结束,例如你不必手动实例化依赖关系的所有依赖关系。

从实例中获取依赖关系也违反了Demeter法则 ,该法律规定您不应该深入研究合作者。 一个实例只能与其直接的合作者交谈。 这对于Singleton和ServiceLocator都是一个问题。

全球国家

全局状态的问题也有所缓解,因为当你在testing之间实例化一个新的服务定位器时,所有先前创build的实例也被删除(除非你犯了错误并将它们保存在SL中的静态属性中)。 这当然不适用于由SLpipe理的class级中的任何全球状态。

另请参阅服务定位器与dependency injection的 Fowler进行更深入的讨论。


关于您的更新和Sebastian Bergmann关于使用Singletons的testing代码的链接文章的注释:Sebastian确实没有build议使用Singleons来解决问题。 这只是一种制作代码的方法,否则将无法testing更多的可testing代码。 但它仍然是有问题的代码。 事实上,他明确指出:“只因为可以,不代表你应该”。

服务定位器模式是反模式。 它不能解决暴露依赖的问题(你不能通过查看类的定义来判断它的依赖是什么,因为它们没有被注入,而是被从服务定位器中抽出)。

所以,你的问题是:服务定位器为什么好? 我的答案是:他们不是。

避免,避免,避免。

服务容器按照单例模式隐藏相关性。 您可能想build议使用dependency injection容器,因为它具有服务容器的所有优点,但是没有(据我所知)服务容器具有的缺点。

据我了解,两者之间唯一的区别是,在服务容器中,服务容器是被注入的对象(从而隐藏依赖),当你使用DIC时,DIC为你注入适当的依赖关系。 由DICpipe理的class级完全没有意识到由DICpipe理的事实,因此您具有较less的耦合,清晰的依赖性和快乐的unit testing。

这是一个很好的问题,因此解释了两者的区别: dependency injection和服务定位模式有什么区别?

因为您可以轻松地用“replace服务容器”中的对象
1)inheritance(对象pipe理器类可以被inheritance,方法可以被重写)
2)改变configuration(在Symfony的情况下)

而且,单身人士不仅因为高度的偶合,而且因为他们是单身 。 几乎所有types的对象都是错误的架构。

使用“纯”的DI(在构造函数中),您将付出非常大的代价 – 所有对象都应该在构造函数中传递之前创build。 这将意味着更多的使用内存和更less的性能。 另外,并不总是可以在构造函数中创build和传递对象 – 可以创build依赖关系链…我的英语还不够完善,请在Symfony文档中阅读它。

对于我来说,我尽量避免使用全局常量,单因素有一个简单的原因,有时候我可能需要运行API。

例如,我有前端和pipe理员。 在pipe理员内部,我希望他们能够以用户身份login。 考虑pipe理员内部的代码。

 $frontend = new Frontend(); $frontend->auth->login($_GET['user']); $frontend->redirect('/'); 

这可能会build立新的数据库连接,新的logging器等前端初始化,并检查用户是否真正存在,有效等。它也将使用适当的单独的cookie和位置服务。

我对单身人士的想法是 – 你不能在父母内部两次添加相同的对象。 例如

 $logger1=$api->add('Logger'); $logger2=$api->add('Logger'); 

会给你一个单一的实例和两个variables指向它。

最后如果你想使用面向对象的开发,那么使用对象,而不是类。