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

时间:2011-05-17 17:30:39

标签: php oop design-patterns frameworks

我们都知道糟糕的单身人士是怎样的,因为他们隐藏了依赖关系和other reasons

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

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

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

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

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

添加

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

DI + Singleton解决了这个问题:

<?php
class Client {

    public function doSomething(Singleton $singleton = NULL){

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

        // ...
    }
}
?>

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

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

5 个答案:

答案 0 :(得分:75)

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

单一责任原则

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

联轴器

由于静态方法调用,单身人士通常会硬编码到您的应用程序中,这会导致代码中出现tight coupled and hard to mock dependencies。另一方面,SL只是一个类,可以注入。因此,虽然你所有的分类都依赖于它,但至少它是一个松散耦合的依赖。因此,除非您将ServiceLocator实现为Singleton本身,否则它会更好,也更容易测试。

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

隐藏的依赖关系

虽然存在很多隐藏依赖关系的问题。当您将定位器注入到消费类时,您将不会知道任何依赖项。但与Singleton相比,SL通常会实例化幕后所需的所有依赖项。因此,当您获取服务时,您最终不会像Misko Hevery in the CreditCard example那样结束,例如你不必手动实例化依赖关系的所有依赖。

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

全球国家

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

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


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

答案 1 :(得分:42)

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

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

避免,避免,避免。

答案 2 :(得分:3)

服务容器以Singleton模式的形式隐藏依赖关系。您可能希望建议使用依赖注入容器,因为它具有服务容器的所有优点但尚未(据我所知)服务容器具有的缺点。

据我了解,两者之间的唯一区别是在服务容器中,服务容器是被注入的对象(因此隐藏了依赖关系),当您使用DIC时,DIC会为您注入适当的依赖关系。由DIC管理的类完全忽略了它由DIC管理的事实,因此您具有较少的耦合,明确的依赖性和快乐的单元测试。

这是一个很好的问题,解释了两者的区别:What's the difference between the Dependency Injection and Service Locator patterns?

答案 3 :(得分:1)

因为您可以通过
轻松替换Service Container中的对象 1)继承(对象管理器类可以继承,方法可以覆盖)
2)改变配置(在Symfony的情况下)

而且,单身人士不仅是因为高耦合,而是因为他们是_ _吨。几乎所有类型的对象都是错误的架构。

使用'纯'DI(在构造函数中),您将付出非常大的代价 - 所有对象都应该在构造函数中传递之前创建。这将意味着更多的内存和更低的性能。此外,并不总是可以在构造函数中创建和传递对象 - 可以创建依赖项链...我的英语不够完整,可以在Symfony文档中阅读它。

答案 4 :(得分:0)

对我来说,我试图避免全局常量,单例因为一个简单的原因,有些情况下我可能需要运行API。

例如,我有前端和管理员。在管理员内部,我希望他们能够以用户身份登录。考虑一下admin中的代码。

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

这可以为前端初始化建立新的数据库连接,新记录器等,并检查用户是否确实存在,有效等。它还将使用适当的单独cookie和位置服务。

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

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

会留下一个实例,两个变量都指向它。

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