【问题标题】:Understanding IoC Containers and Dependency Injection了解 IoC 容器和依赖注入
【发布时间】:2013-09-01 20:45:10
【问题描述】:

我的理解:

所以此时我开始尝试将 IoC 容器用于更复杂的场景。到目前为止,似乎为了使用 IoC 容器,对于我想要创建的几乎任何具有它想要在 IoC 容器中定义的依赖项的类,我都被限制在一个 has-a 关系中。如果我想创建一个继承类的类,但前提是父类是以特定方式创建的,它是在 IoC 容器中注册的。

例如:我想创建一个 mysqli 的子类,但我想在 IoC 容器中注册这个类,以便仅使用我之前在 IoC 容器中注册的方式构造的父类进行实例化。如果不复制代码,我想不出一种方法来做到这一点(而且由于这是一个学习项目,我试图让它尽可能地“纯粹”)。 Here are some more examples of what I am trying to describe.

以下是我的一些问题:

  • 在不违反 OOP 的某些原则的情况下,我在上面尝试做的事情是否可行?我知道在 c++ 中我可以使用动态内存和复制构造函数来完成它,但是我无法在 php.ini 中找到那种功能。 (我承认我在使用 __construct 之外的任何其他魔术方法方面几乎没有经验,但是如果我理解正确,从阅读和 __clone 中,我无法在构造函数中使用它来使被实例化的子类成为一个克隆父类的实例)。
  • 相对于 IoC,我的所有依赖类定义应该放在哪里? (我的 IoC.php 是否应该只有一堆 require_once('dependencyClassDefinition.php') 在顶部?我的直觉反应是有更好的方法,但我还没有想出一个)
  • 我应该在哪个文件中注册我的对象?目前在类定义后的 IoC.php 文件中执行所有对 IoC::register() 的调用。
  • 在注册需要依赖的类之前,是否需要在 IoC 中注册依赖?由于在实际实例化在 IoC 中注册的对象之前我不会调用匿名函数,所以我猜不是,但这仍然是一个问题。
  • 还有什么我忽略了我应该做或使用的事情吗?我试图一步一步来,但我也不想知道我的代码是可重用的,最重要的是,对我的项目一无所知的人可以阅读和理解它。李>

【问题讨论】:

    标签: php oop inheritance dependency-injection inversion-of-control


    【解决方案1】:

    简单地说(因为它不仅是 OOP 世界的问题),依赖是组件 A 需要(依赖)组件 B 来完成它应该做的事情的情况。该词也用于描述此场景中的依赖组件。用 OOP/PHP 术语来说,请考虑以下带有强制性汽车类比的示例:

    class Car {
    
        public function start() {
            $engine = new Engine();
            $engine->vroom();
        }
    
    }
    

    Car 依赖于 EngineEngineCar依赖关系。这段代码很糟糕,因为:

    • 依赖是隐式的;在您检查Car 的代码之前,您不会知道它的存在
    • 类是紧密耦合的;您不能将 Engine 替换为用于测试目的的 MockEngine 或在不修改 Car 的情况下扩展原始 TurboEngine
    • 让汽车自己制造引擎看起来有点傻,不是吗?

    依赖注入是解决所有这些问题的一种方法,方法是明确Car 需要Engine 并明确提供一个:

    class Car {
    
        protected $engine;
    
        public function __construct(Engine $engine) {
            $this->engine = $engine;
        }
    
        public function start() {
            $this->engine->vroom();
        }
    
    }
    
    $engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine
    $car = new Car($engine);
    

    以上是一个构造函数注入的例子,其中依赖(被依赖的对象)通过类构造函数提供给依赖(消费者)。另一种方法是在Car 类中公开setEngine 方法并使用它来注入Engine 的实例。这称为 setter 注入,主要用于应该在运行时交换的依赖项。

    任何重要的项目都由一堆相互依赖的组件组成,并且很容易忘记快速注入的内容。 依赖注入容器是一个对象,它知道如何实例化和配置其他对象,知道它们与项目中其他对象的关系是什么,并为您进行依赖注入。这使您可以集中管理所有项目的(相互)依赖关系,更重要的是,可以更改/模拟其中的一个或多个,而无需在代码中编辑一堆位置。

    让我们抛开汽车的类比,以 OP 试图实现的目标为例。假设我们有一个依赖于mysqli 对象的Database 对象。假设我们想使用一个非常原始的依赖检测容器类DIC,它公开了两个方法:register($name, $callback) 注册一种在给定名称下创建对象的方法,resolve($name) 从该名称获取对象。我们的容器设置如下所示:

    $dic = new DIC();
    $dic->register('mysqli', function() {
        return new mysqli('somehost','username','password');
    });
    $dic->register('database', function() use($dic) {
        return new Database($dic->resolve('mysqli'));
    });
    

    请注意,我们告诉容器从自身获取mysqli 的实例 以组装Database 的实例。然后要获得一个自动注入其依赖项的Database 实例,我们只需:

    $database = $dic->resolve('database');
    

    这就是它的要点。一个更复杂但仍然相对简单且易于掌握的 PHP DI/IoC 容器是Pimple。查看其文档以获取更多示例。


    关于OP的代码和问题:

    • 不要为您的容器使用静态类或单例(或其他任何东西); they're both evil。改为查看 Pimple。
    • 决定您是希望 mysqliWrapper扩展 mysql 还是依赖
    • 通过从mysqliWrapper 中调用IoC,您将一个依赖项交换为另一个依赖项。你的对象不应该知道或使用容器;否则它不再是 DIC,而是服务定位器(反)模式。
    • 在将类文件注册到容器之前,您不需要require,因为您根本不知道是否要使用该类的对象。在一处完成所有容器设置。如果你不使用自动加载器,你可以在你注册到容器的匿名函数中require

    其他资源:

    【讨论】:

    • 通过这个例子,看起来你正在创建的工厂是一个美化的关联数组(这个$dic 变量)。 Pimple 是一个美化的关联数组。调用 $dic->resolve() 是一个服务定位器,并且会成为您对象 API 的骗子。您阅读了 Fowler 和干净的代码讨论 - 但提供了 pimple 和反模式作为解决方案。我认为您需要重新考虑$database = 上的部分...否则很高兴看到有人声明不使用静态:-)
    • @Jimbo 如果您将它作为一个服务定位器使用,它只是一个服务定位器,即将它传递给您的对象,这是我在回答中明确警告的。不过,使用类似的简单容器在靠近应用程序入口点的某个地方构建对象图并没有什么“反模式”。顺便说一句,在将关联数组/哈希图作为极少数非标量核心类型之一的语言中,有很多东西可以发展成“美化的关联数组”;-)
    • 大声笑,公平点。但是,$database = $dic->resolve('database'); 和服务定位器的警告似乎没有关联。我没有得到使用这个的警告。如果你对这类东西感兴趣,让我大开眼界:link
    • 很好的答案,感谢您花时间解释 - 确实有助于理解应用程序设计等的大局! (无关的,你的$engine->vroom(); 勾起了我一段非常古老的记忆,让我想起了 Atari ST 上的 vroom,上帝我喜欢那个游戏,声音在当时是一种现象,无论如何要回去工作......)
    • SuperDuperTurboEnginePlus,我哭了 :D 好例子 :)
    猜你喜欢
    • 1970-01-01
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    相关资源
    最近更新 更多