哪个更好:依赖注入+注册表或依赖注入或全局注册?

时间:2010-08-19 19:00:40

标签: php dependency-injection registry global-variables

首先,我想将此问题仅限于Web开发。因此,只要语言用于Web开发,这就是语言无关的。就个人而言,我是从PHP的背景来看这个。

我们经常需要使用来自多个范围的对象。例如,我们可能需要在正常范围内使用数据库类,但也需要在控制器类中使用。如果我们在正常范围内创建数据库对象,那么我们无法从控制器类内部访问它。我们希望避免在不同的范围内创建两个数据库对象,因此无论范围如何,都需要一种重用数据库类的方法。为此,我们有两个选择:

  1. 使数据库对象成为全局数据库,以便可以从任何地方访问它。
  2. 以例如控制器构造函数的参数的形式将数据库类传递给控制器​​类。这称为依赖注入(DI)。
  3. 当许多类涉及许多不同范围内的所有要求对象时,问题变得更加复杂。在这两个解决方案中,这都成了问题,因为如果我们将每个对象都设置为全局,那么我们就会在全局范围内输入太多噪声,如果我们将太多参数传递给类,则该类变得更难以管理。

    因此,在这两种情况下,您经常会看到使用注册表。在全局情况下,我们有一个全局的注册表对象,然后将所有对象和变量添加到任何对象中,但只将一个变量(注册表)放入全局范围。在DI情况下,我们将注册表对象传递给每个类,将参数数量减少到1。

    就个人而言,我使用后一种方法,因为有很多文章主张使用全局变量,但我遇到了两个问题。首先,注册表类将包含大量的递归。例如,注册表类将包含数据库类所需的数据库登录变量。因此,我们需要将注册表类注入数据库。但是,许多其他类将需要该数据库,因此需要将数据库添加到注册表中,创建一个循环。现代语言可以处理这个问题还是会导致巨大的性能问题?请注意,全局注册表不会受此影响,因为它没有传递给任何东西。

    其次,我将开始将大量数据传递给不需要它的对象。我的数据库不关心我的路由器,但路由器将与数据库连接详细信息一起传递到数据库。这通过递归问题变得更糟,因为如果路由器具有注册表,则注册表具有数据库和注册表并且注册表被传递到数据库,然后数据库通过路由器传递给自己(即,我可以{ {1}}来自数据库类`)。

    此外,除了更复杂之外,我没有看到DI给我的是什么。我必须将一个额外的变量传递给每个对象,我必须使用$this->registry->router->registry->database而不是$this->registry->object->method()的注册表对象。现在这显然不是一个大问题,但如果它没有给我任何关于全球方法的东西,它似乎是不必要的。

    显然,当我在没有注册表的情况下使用DI时,这些问题不存在,但是我必须“手动”传递每个对象,导致类构造函数具有荒谬的参数数量。

    鉴于这两个版本的DI存在这些问题,是不是全球注册表优越?通过DI使用全球注册表,我失去了什么?

    在讨论DI vs Globals时经常提到的一点是全局变量会抑制你正确测试程序的能力。全局变量究竟是如何阻止我测试DI不会的程序?我在很多地方都读到这是因为全局可以从任何地方改变,因此难以模仿。但是,在我看来,至少在PHP中,对象是通过引用传递的,更改某个类中的注入对象也会在注入它的任何其他类中更改它。

2 个答案:

答案 0 :(得分:13)

让我们逐一解决这个问题。

  

首先,注册表类将包含大量的递归

您不必将Registry类注入数据库类。你也可以have dedicated methods on the Registry to create the required classes。或者,如果您注入注册表,您可以简单地不存储它,但只能从中获取正确实例化类所需的内容。没有递归。

  

请注意,全局注册表不会受此影响,因为它不会传递给任何内容。

注册表本身可能没有递归,但注册表中的对象很可能有循环引用。当Garbage Collector无法正确收集这些文件时,在5.3之前使用PHP版本从注册表中取消对象时,这可能会导致内存泄漏。

  

其次,我将开始将大量数据传递给不需要它的对象。我的数据库不关心我的路由器,但路由器将与数据库连接详细信息一起传递到数据库。

真。但这就是注册表的用途。它与将$ _GLOBALS传递到您的对象没有太大区别。如果你不想这样,不要使用注册表,但只传递类实例所需的参数,使其处于有效状态。或者根本不存储它。

  

我可以做$ this-> registry-> router-> registry->数据库

路由器不太可能公开一个公共方法来获取注册表。您无法从database$this转到router,但您可以直接转到database。当然。这是一个注册表。那是你写的。如果要将Registry存储在对象中,可以将它们包装到一个隔离接口中,该接口只允许访问其中包含的数据子集。

  

显然,当我在没有注册表的情况下使用DI时,这些问题不存在,但是我必须“手动”传递每个对象,导致类构造函数具有荒谬的参数数量。

不一定。使用构造函数注入时,可以将参数数量限制为将对象置于有效状态所必需的参数数量。其余的可选依赖项也可以通过setter注入设置。此外,没有人阻止您在Array或Config对象中添加参数。或者使用Builders

  

鉴于这两个版本的DI存在这些问题,是不是全球注册表优越?通过DI使用全球注册表,我失去了什么?

当您使用全局注册表时,您将此依赖项紧密耦合到类。这意味着如果没有这个具体的Registry类,就不能使用using类。您假设只有这个注册表而不是不同的实现。在注射家属时,你可以自由地注射任何履行了依赖关系的责任。

  

在讨论DI vs Globals时经常提到的一点是全局变量会抑制你正确测试程序的能力。全局变量究竟是如何阻止我测试DI不会的程序的?

它们不会阻止您测试代码。 They just make it harder.单元测试时,您希望系统处于已知且可重现的状态。如果您的代码有dependencies on the global state,则必须在每次测试运行时创建此状态。

  

我在很多地方都读到这是因为全局可以从任何地方改变,因此难以模拟

正确,如果一个测试更改了全局状态,如果不更改它,它可能会影响下一个测试。这意味着除了将测试对象设置为已知状态之外,您还必须努力重新创建环境。如果只有一个依赖项,这可能很容易,但如果有很多依赖,那么它们也依赖于全局状态。你最终会进入Dependency Hell

答案 1 :(得分:5)

我会将此作为答案发布,因为我想要包含代码。

我已经基于传递对象与使用global进行基准测试。我基本上创建了一个相对简单的对象,但其中一个具有自引用和嵌套对象。

结果:

Passed Completed in 0.19198203086853 Seconds
Globaled Completed in 0.20970106124878 Seconds

如果我删除嵌套对象和自引用,结果是相同的......

是的,似乎这两种不同的传递数据的方法之间没有真正的性能差异。因此,做出更好的架构选择(恕我直言,这是依赖注入)......

剧本:

$its = 10000;
$bar = new stdclass();
$bar->foo = 'bar';
$bar->bar = $bar;
$bar->baz = new StdClass();
$bar->baz->ar = 'bart';

$s = microtime(true);
for ($i=0;$i<$its;$i++) passed($bar);
$e = microtime(true);
echo "Passed Completed in ".($e - $s) ." Seconds\n";

$s = microtime(true);
for ($i=0;$i<$its;$i++) globaled();
$e = microtime(true);
echo "Globaled Completed in ".($e - $s) ." Seconds\n";

function passed($bar) {
    is_object($bar);
}

function globaled() {
    global $bar;
    is_object($bar);
}

在5.3.2上测试