如果单例是坏的,那么为什么服务容器是好的?

2022-08-30 07:47:42

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

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

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

但是,为什么服务提供商不如纯粹的单例那么糟糕呢?

服务提供商也隐藏了依赖关系,他们只是包装了第一个 istance 的创建。因此,我真的很难理解为什么我们应该使用服务提供商而不是单例。

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

我想补充一点:现在单例并不是那么邪恶,PHPUnit的创建者在这里解释了这一点:

DI + Singleton解决了这个问题:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

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

        // ...
    }
}
?>

这是非常聪明的,即使这并不能解决所有问题。

除了 DI 和服务容器之外,是否有任何好的可接受的解决方案来访问此帮助程序对象?


答案 1

可以说,服务定位器只是两害相权取其轻。“较小”归结为这四个差异(至少我现在想不出任何其他差异):

单一责任原则

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

耦合

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

但是,使用 ServiceLocator 的所有类现在都将依赖于 ServiceLocator,这也是一种耦合形式。这可以通过使用 ServiceLocator 的接口来缓解,这样您就不会绑定到具体的 ServiceLocator 实现,但您的类将依赖于某种定位器的存在,而根本不使用 ServiceLocator 会显著提高重用率。

隐藏的依赖关系

但是,隐藏依赖关系的问题非常多。当您只是将定位器注入使用类时,您将不知道任何依赖项。但与 Singleton 相反,SL 通常会在后台实例化所需的所有依赖项。因此,当您获取服务时,您不会像信用卡示例中的Misko Hevery那样结束,例如,您不必手动实例化依赖项的所有缺陷。

从实例内部获取依赖项也违反了 Demeter 定律,该定律规定您不应深入研究协作者。实例应仅与其直接协作者通信。这是 Singleton 和 ServiceLocator 的问题。

全局状态

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

另请参阅 Fowler 上的 Service Locator vs Dependency Injection,以进行更深入的讨论。


关于您的更新和Sebastian Bergmann关于测试使用单例代码的链接文章的注释:Sebastian绝不表明建议的解决方法使使用Singleons成为一个问题。这只是使代码更易于测试的一种方法。但它仍然是有问题的代码。事实上,他明确指出:“仅仅因为你可以,并不意味着你应该”。


答案 2

服务定位器模式是反模式。它不能解决公开依赖项的问题(您无法通过查看类的定义来判断其依赖项是什么,因为它们没有被注入,而是被从服务定位器中拉出)。

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

避免,避免,避免。


推荐