Rup*_*ott 16 php registry dependency-injection global-variables
首先,我想将此问题仅限于Web开发.因此,只要语言用于Web开发,这就是语言无关的.就个人而言,我是从PHP的背景来看这个.
通常我们需要使用来自多个范围的对象.例如,我们可能需要在正常范围内使用数据库类,但也需要在控制器类中使用.如果我们在正常范围内创建数据库对象,那么我们无法从控制器类内部访问它.我们希望避免在不同的范围内创建两个数据库对象,因此无论范围如何,都需要一种重用数据库类的方法.为此,我们有两种选择:
当许多类涉及许多不同范围内的所有要求对象时,问题变得更加复杂.在这两种解决方案中,这都成了问题,因为如果我们将每个对象都设置为全局,那么我们就会在全局范围内输入太多噪声,如果我们将太多参数传递给类,则该类变得更难以管理.
因此,在这两种情况下,您经常会看到使用注册表.在全局情况下,我们有一个全局的注册表对象,然后将所有对象和变量添加到任何对象中,但只将一个变量(注册表)放入全局范围.在DI情况下,我们将注册表对象传递给每个类,将参数数量减少到1.
就个人而言,我使用后一种方法,因为许多文章主张使用全局变量,但我遇到了两个问题.首先,注册表类将包含大量的递归.例如,注册表类将包含数据库类所需的数据库登录变量.因此,我们需要将注册表类注入数据库.但是,许多其他类将需要该数据库,因此需要将数据库添加到注册表中,创建一个循环.现代语言可以处理这个问题还是会导致巨大的性能问题?请注意,全局注册表不会受此影响,因为它没有传递给任何东西.
其次,我将开始将大量数据传递给不需要它的对象.我的数据库不关心我的路由器,但路由器将与数据库连接详细信息一起传递到数据库.这通过递归问题变得更糟,因为如果路由器具有注册表,则注册表具有数据库和注册表并且注册表被传递到数据库,然后数据库通过路由器传递给自己(即我可以$this->registry->router->registry->database从在数据库类里面`).
此外,除了更复杂之外,我没有看到DI给了我什么.我必须将一个额外的变量传递给每个对象,我必须使用注册表对象$this->registry->object->method()而不是$registry->object->method().现在这显然不是一个大问题,但如果它没有给我任何关于全球方法的东西,它似乎是不必要的.
显然,当我在没有注册表的情况下使用DI时,这些问题不存在,但是我必须"手动"传递每个对象,导致类构造函数具有荒谬的参数数量.
鉴于这两个版本的DI存在这些问题,是不是全球注册表优越?通过在DI上使用全球注册表,我失去了什么?
在讨论DI vs Globals时经常提到的一件事是全局变量会抑制你正确测试程序的能力.全局变量究竟是如何阻止我测试DI不会的程序?我在很多地方都读到这是因为全局可以从任何地方改变,因此难以模仿.但是,在我看来,至少在PHP中,对象是通过引用传递的,更改某个类中的注入对象也会在注入它的任何其他类中更改它.
Gor*_*don 13
让我们逐一解决这个问题.
首先,注册表类将包含大量的递归
您不必将Registry类注入数据库类.您也可以在Registry上使用专用方法为您创建所需的类.或者,如果您注入注册表,您可以简单地不存储它,但只能从中获取正确实例化类所需的内容.没有递归.
请注意,全局注册表不会受此影响,因为它没有传递给任何东西.
注册表本身可能没有递归,但注册表中的对象很可能具有循环引用.当垃圾收集器无法正确收集这些对象时,在5.3之前使用PHP版本从注册表中取消对象时,这可能会导致内存泄漏.
其次,我将开始将大量数据传递给不需要它的对象.我的数据库不关心我的路由器,但路由器将与数据库连接详细信息一起传递到数据库.
真正.但这就是注册表的用途.它与将$ _GLOBALS传递到您的对象没有太大区别.如果你不想这样,不要使用注册表,但只传递类实例所需的参数,使其处于有效状态.或者根本不存储它.
我可以做$ this-> registry-> router-> registry-> database
路由器不太可能公开一个公共方法来获取注册表.您将无法去database从$this通过router,但你可以去database直接.当然.这是一个注册表.那是你写的.如果要将Registry存储在对象中,可以将它们包装到隔离接口中,该接口仅允许访问其中包含的数据子集.
显然,当我在没有注册表的情况下使用DI时,这些问题不存在,但是我必须"手动"传递每个对象,导致类构造函数具有荒谬的参数数量.
不必要.使用构造函数注入时,可以将参数数量限制为将对象置于有效状态所必需的参数数量.其余的可选依赖项也可以通过setter注入设置.此外,没有人阻止您在Array或Config对象中添加参数.或者使用Builders.
鉴于这两个版本的DI存在这些问题,是不是全球注册表优越?通过在DI上使用全球注册表,我失去了什么?
当您使用全局注册表时,您将此依赖项紧密耦合到类.这意味着如果没有这个具体的Registry类,就不能使用using类.您假设只有这个注册表而不是不同的实现.注入依赖项时,您可以自由地注入任何履行依赖项责任的内容.
在讨论DI vs Globals时经常提到的一件事是全局变量会抑制你正确测试程序的能力.全局变量究竟是如何阻止我测试DI不会的程序?
它们不会阻止您测试代码.他们只是让它变得更难.单元测试时,您希望系统处于已知且可重现的状态.如果您的代码依赖于全局状态,则必须在每次测试运行时创建此状态.
我在很多地方都读到这是因为全局可以从任何地方改变,因此难以模仿
正确,如果一个测试更改了全局状态,如果不更改它,它可能会影响下一个测试.这意味着除了将测试对象设置为已知状态之外,您还必须努力重新创建环境.如果只有一个依赖项,这可能很容易,但如果有很多依赖,那么它们也依赖于全局状态.你最终会在Dependency Hell.
我会将此作为答案发布,因为我想要包含代码.
我已经通过对象传递对象与使用global.我基本上创建了一个相对简单的对象,但其中一个具有自引用和嵌套对象.
结果:
Passed Completed in 0.19198203086853 Seconds
Globaled Completed in 0.20970106124878 Seconds
Run Code Online (Sandbox Code Playgroud)
如果我删除嵌套对象和自引用,结果是相同的...
所以是的,似乎这两种不同的传递数据的方法之间没有真正的性能差异.因此,做出更好的架构选择(恕我直言,这是依赖注入)...
剧本:
$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);
}
Run Code Online (Sandbox Code Playgroud)
测试5.3.2
| 归档时间: |
|
| 查看次数: |
2209 次 |
| 最近记录: |