【问题标题】:Accessing the DI container访问 DI 容器
【发布时间】:2010-03-24 01:31:01
【问题描述】:

我正在开始一个新项目并建立工作基础。有几个问题出现了,我可能会在这里问很多,希望我能找到一些答案。

第一步是处理对象的依赖关系。我决定使用依赖注入设计模式,我对它有点陌生,为应用程序处理所有这些。

在实际编码时,我遇到了一个问题。如果一个类有多个依赖项,而您想通过构造函数传递多个依赖项(这样在实例化对象后就无法更改它们)。

如何在不传递依赖数组的情况下使用 call_user_func_array()、eval() 或 Reflection 来做到这一点?这就是我要找的:

<?php

class DI
{
    public function getClass($classname)
    {
        if(!$this->pool[$classname]) {
            # Load dependencies
            $deps = $this->loadDependencies($classname);

            # Here is where the magic should happen
            $instance = new $classname($dep1, $dep2, $dep3);

            # Add to pool
            $this->pool[$classname] = $instance;

            return $instance;
        } else {
                return $this->pool[$classname];
        }
    }
}

再次,我想避免调用类的最昂贵的方法。还有其他建议吗?

另外,我如何访问类内部的 DI 类,例如,在需要访问不同模型的控制器中?我应该静态调用它还是将它传递给每个需要它的类?我认为最后一个想法是不可行的。

谢谢大家。

【问题讨论】:

    标签: php dependency-injection


    【解决方案1】:

    [在我开始之前,让我说我主要是一名 Java 程序员——只有一点 PHP 知识。但我会简单地尝试在没有语言细节的情况下理解最重要的概念。]

    依赖注入基于两部分代码:

    1. 建设
    2. 执行

    在最极端的情况下,在执行部分中找不到new 运算符。所有这些都被移到建筑部分。 (在实践中,这将被淡化。)

    所有的构造都发生在构造部分。它创建自下而上执行所需的对象图。所以让我们假设,它应该构造 A:

    • A 依赖于 B,并且
    • B 依赖于 C。

    然后

    • 先构造C。
    • 然后以C为参数构造B。
    • 然后以B为参数构造A。

    所以 C 不必作为构造函数参数传递给 A。这个小例子并没有足够有力地说明,这在多大程度上将必须传递的对象数量减少到相当少的数量。

    依赖注入器本身不应该被传递到执行部分。这是每个人(包括我自己)第一次接触 DI 时都试图犯的基本错误之一。问题是,这将完全模糊构建和执行之间的界限。另一种说法是,它会违反Law of Demeter。或者用模式来说:它最终会将依赖注入模式“降级”为服务定位器模式。如果这真的是一种退化,这是值得商榷的,但无论如何滥用依赖注入器作为服务定位器通常不是一个好主意。

    因此,当您需要在执行期间为您构建的对象之一赋予生成其他对象的能力时,您只需传递简单的提供者(Java DI 框架Guice 使用的术语),而不是传递依赖注入器。这些是相当简单的类,只能创建某种对象。它们与工厂有相似之处。

    首先尝试将所需的依赖项直接传递给构造函数。

    所以,总结一下:

    • 自下而上构建对象。
    • 只传递创建对象所需的尽可能少的依赖项。
    • 完成后,开始执行。
    • 在执行期间,您仍然可以使用 Providers 获取新创建的对象。

    但不要太过分:在没有 Provider 的情况下仍然可以创建简单的对象 :-)

    现在,您所要做的就是将这些东西翻译成高质量的代码。也许其他人可以通过一些 PHP 示例来帮助您。

    附录:关于提供者的更多信息

    如上所述,“提供者”(一个专门的工厂)的概念有点特定于 Java DI 框架 Guice。这个框架可以自动为任何类型的对象创建一个Provider。但是,该概念通常对 DI 有用。唯一的区别是,如果没有 Guice 或类似框架的帮助,您必须自己编写提供程序——但这很容易:

    假设 B 依赖于 C。

    • 如果 B 只需要一个固定的 C 实例,那么您就不需要提供程序 - 您可以简单地使用构造函数参数 C 构造 B。
    • 如果 B 在执行过程中需要创建更多的 C 实例,那么只需编写一个名为 CProvider 的类,使用 get() 方法,可以创建一个新的 C 实例。然后将 CProvider 的实例传递给B的构造函数,并将Provider存储在B的实例字段中。现在B在需要C的新实例时可以调用cProvider.get()。

    提供程序是构造代码的一部分,因此您可以使用new C(...)!另一方面,它们不是执行代码的一部分,所以你不应该在那里有任何执行逻辑。

    CProvider 当然可以传递给多个构造函数。您还可以编写多个版本CProvider1、CProvider2、... - 每个版本都可以构造具有不同属性的不同版本的 C 对象。或者你简单地用不同的参数多次实例化CProvider。

    【讨论】:

    • 有见地,感谢您的解释。我知道服务定位器模式,这不是我想要完成的。能否请您详细说明一下提供程序的使用情况?
    • 添加了一个附录。如果您需要更多信息,请告诉我。
    • 非常感谢您的帮助。基本上,我需要为每个多实例依赖项传递所需的提供程序。这些基本上是工厂模式,但特定于为某个目的创建对象(可能是各种类,例如输出驱动程序)并将它们返回给依赖类。听起来更复杂:D
    • 我认为,最初的努力是值得的——因为当使用 DI(有或没有框架)时,您最终会得到甚至可以在下一个应用程序中重用的组件!从长远来看,这是 IMO DI 的最大好处,甚至比增加代码的可测试性更重要 :-)
    • 很棒的解释。在 C# 中,我喜欢只使用 Func 作为我的提供者。这很简单。
    【解决方案2】:

    您应该考虑使用IOC 容器来为您管理依赖项。一个好的 IOC 容器应该为您处理依赖构造函数之间的依赖关系。

    现有question 询问有关 PHP 的 IOC 容器选项。

    【讨论】:

      【解决方案3】:

      看起来您正在尝试推出自己的依赖注入容器。为什么不使用已经存在的,例如Symfony、Crafty 或Sphicy?

      【讨论】:

      • 我们的目标是避免使用任何第三方库,除非绝对必要(主要是由于许可问题)。
      • @andre:Symfony 在 MIT 许可下获得许可。阅读它,它基本上说“做你想做的”:opensource.org/licenses/mit-license.php
      • 我正在创建自己的许可证,不想将归属权归属于第三方库或软件(项目要求的一部分),因此我需要创建自己的容器。
      猜你喜欢
      • 2021-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多