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

dyn*_*mic 90 php oop frameworks design-patterns

我们都知道单身人士有多糟糕,因为他们隐藏了依赖关系和其他原因.

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

为了解决这个问题,我被告知使用一个所谓的"对象管理器"(或像symfony这样的服务容器),它在内部存储对服务的每个引用(记录器等).

但为什么服务提供商不像纯粹的单身人士那样糟糕?

服务提供商也隐藏了依赖关系,他们只是创建了第一个istance.所以我真的很难理解为什么我们应该使用服务提供商而不是单身人士.

PS.我知道不要隐藏依赖关系我应该使用DI(如Misko所述)

我想补充一点:这些天单身人士不是那么邪恶,PHPUnit的创建者在这里解释:

DI + Singleton解决了这个问题:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

        if ($singleton === NULL) {
            $singleton = Singleton::getInstance();
        }

        // ...
    }
}
?>
Run Code Online (Sandbox Code Playgroud)

这很聪明,即使这根本不能解决所有问题.

除DI和Service Container之外是否有任何可接受的解决方案来访问此帮助程序对象?

Gor*_*don 75

服务定位器只是两个邪恶中较小的一个."较小的"沸腾到这四个差异(至少我现在不能想到任何其他的):

单一责任原则

服务容器不像Singleton那样违反单一责任原则.单身人士混合对象创建和业务逻辑,而服务容器则严格负责管理应用程序的对象生命周期.在这方面,服务容器更好.

耦合

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

但是,使用ServiceLocator的所有类现在都依赖于ServiceLocator,这也是一种耦合形式.这可以通过使用ServiceLocator的接口来减轻,因此您不必绑定到具体的ServiceLocator实现,但是您的类将依赖于某种Locator的存在,而根本不使用ServiceLocator会显着地重用.

隐藏的依赖关系

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

从实例内部获取依赖项也违反了Demeter法则,该法规定您不应该深入研究协作者.一个实例应该只与其直接的合作者交谈.这是Singleton和ServiceLocator的问题.

全球国家

全局状态的问题也有所缓解,因为当您在测试之间实例化一个新的服务定位器时,所有先前创建的实例也会被删除(除非您犯了错误并将其保存在SL中的静态属性中).当然,对于由SL管理的类中的任何全局状态而言,这都不适用.

另请参阅Fowler on Service Locator vs Dependency Injection以进行更深入的讨论.


关于您的更新的说明以及Sebastian Bergmann关于测试使用Singletons的代码的链接文章:塞巴斯蒂安确实没有提出建议的解决方法使得使用Singleons不是问题.这只是制作代码的一种方法,否则将无法测试更可测试的代码.但它仍然是有问题的代码.事实上,他明确指出:"只因为你可以,并不意味着你应该".


jas*_*son 42

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

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

避免,避免,避免.

  • 看起来你对接口一无所知.类只描述了构造函数签名中的必要接口 - 而这正是他需要知道的.Passed Service Locator应该实现接口,就是这样.如果IDE将检查接口的实现,那么控制任何更改都会非常容易. (5认同)
  • @ yes123:那些说错误的人,他们错了,因为SL是一种反模式.你的问题是"为什么SL好?" 我的回答是:他们不是. (4认同)
  • 我不会争论SL是否是一个anit-pattern,但我要说的是,与单身和全局相比,它是一个更小的邪恶.你不能测试一个依赖于单例的类,但是你肯定可以测试一个依赖于SL的类(可以将SL设计搞砸到无法工作的地方)...所以这是值得的提... (4认同)
  • @Jason你需要传递实现接口的对象 - 而这只是你需要知道的.你只是通过定义类构造函数来限制自己,并且想在构造函数中编写所有类(而不是接口) - 这是一个愚蠢的想法.您只需要界面.您可以使用模拟成功测试此类,您可以轻松地更改行为而无需更改代码,没有额外的依赖关系和耦合 - 这是(通常)我们希望在依赖注入中拥有的内容. (3认同)
  • 当然,我只是把数据库,记录器,磁盘,模板,缓存和用户放在一个单独的"输入"对象中,肯定会比我使用容器更容易分辨我的对象所依赖的依赖项. (2认同)
  • 而且我并不是说单个对象应该处理数据库和模板化的东西,但我的观点是,它可以*发生一个对象依赖于几个独立的对象,在另一个对象中将它们聚合在一起是没有意义的. (2认同)