DI是Singleton和/或静态对象的唯一解决方案吗?

时间:2011-05-16 21:38:19

标签: php oop design-patterns

有人告诉我,单身人士很难测试。

我被告知静态方法/对象也不好。

所以基本上唯一的解决方案似乎是dependency injection

但是......我真的不能习惯DI,举个例子:

在我的框架中,我有一个管理SQL的类。这个类(以及我的许多其他框架)使用单例Logger来记录消息(以及许多其他帮助程序)。

使用DI,我的代码将转向:

global $logger; //> consider i have been instanciated it at the start of my fw

$query = new PreparedQuery($logger);
$query->prepare() etc.

现在看起来并不太糟糕。但是考虑一个需要很多查询的页面,我认为必须在构造函数中每次$logger编写都是多余的,特别是如果你考虑PreparedQuery是否需要构造函数中的许多其他依赖项。

我发现 避免单例的唯一解决方案是在主应用程序中使用一个方法(或只是一个简单的函数)来存储对这个帮助器对象的每个引用(服务定位器/容器)但这并不能解决隐藏依赖关系的问题

那么根据你的经验,除了DI之外,什么是好的模式?

解决方案:

对于每个人来说,PHPunit的创建者解释了如何解决Singleton问题(以及如何使用PHP 5.3解决静态方法测试问题)

如果你问我,那很有意思。

5 个答案:

答案 0 :(得分:3)

请不要使用global 您需要在构造函数中传递$ logger或者传递Service Container(也称为对象管理器,服务定位器,资源管理器)。
来自Symfony框架的变体http://symfony.com/doc/current/book/service_container.html
您可以创建自己的对象管理器,他的方法不应该是静态的。

答案 1 :(得分:3)

那么,在这种情况下,我会建立一个builder(或工厂)。所以你的工厂会为你注入依赖。这样你也可以避免你的全局:

class PreparedQueryFactory {
    protected $logger = null;
    public function __construct($loggger) {
        $this->logger = $logger;
    }
    public function create() {
        return new PreparedQuery($this->logger);
    }
}

这样,你只做一次:

$factory = new PreparedQueryFactory($logger);

然后,只要您需要新查询,只需致电:

$query = $factory->create();

现在,这是一个非常简单的例子。但是如果需要,你可以添加各种复杂的逻辑。但重点是,通过避免代码中的new,您还可以避免管理依赖项。因此,您可以根据需要绕过工厂。

好处是,所有这些都是100%可测试的,因为所有内容都被注入(与使用全局变量相反)。

您也可以使用注册表(也称为服务容器或DI容器),但请确保注入注册表。

答案 2 :(得分:1)

记录通常是 示例,其中静态单例是正常的。你无论如何都不需要嘲笑你的记录,对吗?

答案 3 :(得分:1)

以上答案为您提供了一些想法。我将介绍另一个:实现插件架构。记录器成为一个插件,您可以随时启用/禁用/更改。

简化示例:

class Logger implements Observer {
    public function notify($tellMeWhatHappened) {
         // oh really? let me do xyz
    }
}

class Query implements Observable {
    private $observers = array();

    public function addObserver(Observer $observer) {
        $this->observers[] = $observer;
    }

    public function foo() {
        // great code
        foreach ($this->observers as $observer) { $observer->notify('did not work'); }
    }
}

这会将Logger从构造函数中移除。如果它对于物体的运作不是必不可少的,那就是我喜欢的。

答案 4 :(得分:0)

根据我对Misko Hevery关于DI和new运算符的讨论的理解,问题是你没有足够来实现DI。

Hevery总是说,你不应该将业务逻辑与对象构建混为一谈。但是,在您的示例的两行中,第一行($query = new PreparedQuery($logger);)构造一个对象,然后第二行($query->prepare(/* ... */);)是业务逻辑。

显然,该代码的目标是准备一个查询,而不是担心如何构建PreparedQuery,它应该只在类构造函数中请求一个。{1}}。或者如果它需要能够生成大量的PreparedQueries,它应该要求原型(它将在需要新的时候克隆)或工厂对象。关键是,PreparedQuery有一个记录器的事实并不重要,应该在其他地方处理。

构造函数中“询问你需要什么”的原则原则上很容易理解,虽然我仍在努力为自己解决在各种情况下它在实践中意味着什么,以及如何一直实现它到顶部(“主要方法”或等同物)。但是,我认为这个原则可以说明你遇到的一般问题。 new运营商不应该在首位。