首先,我想将此问题仅限于Web开发。因此,只要语言用于Web开发,这就是语言无关的。就个人而言,我是从PHP的背景来看这个。
我们经常需要使用来自多个范围的对象。例如,我们可能需要在正常范围内使用数据库类,但也需要在控制器类中使用。如果我们在正常范围内创建数据库对象,那么我们无法从控制器类内部访问它。我们希望避免在不同的范围内创建两个数据库对象,因此无论范围如何,都需要一种重用数据库类的方法。为此,我们有两个选择:
当许多类涉及许多不同范围内的所有要求对象时,问题变得更加复杂。在这两个解决方案中,这都成了问题,因为如果我们将每个对象都设置为全局,那么我们就会在全局范围内输入太多噪声,如果我们将太多参数传递给类,则该类变得更难以管理。
因此,在这两种情况下,您经常会看到使用注册表。在全局情况下,我们有一个全局的注册表对象,然后将所有对象和变量添加到任何对象中,但只将一个变量(注册表)放入全局范围。在DI情况下,我们将注册表对象传递给每个类,将参数数量减少到1。
就个人而言,我使用后一种方法,因为有很多文章主张使用全局变量,但我遇到了两个问题。首先,注册表类将包含大量的递归。例如,注册表类将包含数据库类所需的数据库登录变量。因此,我们需要将注册表类注入数据库。但是,许多其他类将需要该数据库,因此需要将数据库添加到注册表中,创建一个循环。现代语言可以处理这个问题还是会导致巨大的性能问题?请注意,全局注册表不会受此影响,因为它没有传递给任何东西。
其次,我将开始将大量数据传递给不需要它的对象。我的数据库不关心我的路由器,但路由器将与数据库连接详细信息一起传递到数据库。这通过递归问题变得更糟,因为如果路由器具有注册表,则注册表具有数据库和注册表并且注册表被传递到数据库,然后数据库通过路由器传递给自己(即,我可以{ {1}}来自数据库类`)。
此外,除了更复杂之外,我没有看到DI给我的是什么。我必须将一个额外的变量传递给每个对象,我必须使用$this->registry->router->registry->database
而不是$this->registry->object->method()
的注册表对象。现在这显然不是一个大问题,但如果它没有给我任何关于全球方法的东西,它似乎是不必要的。
显然,当我在没有注册表的情况下使用DI时,这些问题不存在,但是我必须“手动”传递每个对象,导致类构造函数具有荒谬的参数数量。
鉴于这两个版本的DI存在这些问题,是不是全球注册表优越?通过DI使用全球注册表,我失去了什么?
在讨论DI vs Globals时经常提到的一点是全局变量会抑制你正确测试程序的能力。全局变量究竟是如何阻止我测试DI不会的程序?我在很多地方都读到这是因为全局可以从任何地方改变,因此难以模仿。但是,在我看来,至少在PHP中,对象是通过引用传递的,更改某个类中的注入对象也会在注入它的任何其他类中更改它。
答案 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上测试