了解 IoC 容器和依赖关系注入


Understanding IoC Containers and Dependency Injection

我的理解:

  • 依赖关系是指类 A 的实例需要类 B 的实例来实例化类 A 的新实例。
  • 依赖注入是指通过 ClassA 构造函数中的参数或通过 set~DependencyNameHere~(~DependencyNameHere~ $param( 函数向 ClassA 传递 ClassB 的实例。(这是我不完全确定的领域之一(。
  • IoC 容器是一个单一实例类(在任何给定时间只能实例化 1 个实例(,其中可以注册实例化此项目的这些类的对象的特定方式。下面是一个链接,指向我尝试描述的示例以及我一直使用的 IoC 容器的类定义

因此,在这一点上,我开始尝试将 IoC 容器用于更复杂的方案。到目前为止,似乎为了使用 IoC 容器,我仅限于我想要创建的几乎所有类的 has-a 关系,这些类具有要在 IoC 容器中定义的依赖项。如果我想创建一个继承类的类,但前提是父类是以特定方式创建的,它是在 IoC 容器中注册的,该怎么办?

例如:我想创建一个 mysqli 的子类,但我想在 IoC 容器中注册这个类,以便仅使用以前在 IoC 容器中注册的方式构造的父类进行实例化。我想不出一种在不复制代码的情况下做到这一点的方法(而且由于这是一个学习项目,我试图让它尽可能"纯粹"(。以下是我试图描述的更多示例。

所以这是我的一些问题:

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

简单地说(因为它不仅限于OOP世界的问题(,依赖关系是组件A需要(依赖(组件B来做它应该做的事情的情况。该词还用于描述此方案中的依赖组件。为了用 OOP/PHP 术语来表示这一点,请考虑以下示例和强制性汽车类比:

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

Car取决于Engine. EngineCar依赖。不过这段代码很糟糕,因为:

  • 依赖关系是隐式的;直到你检查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试图实现什么作为例子。假设我们有一个Database对象,具体取决于mysqli对象。假设我们想使用一个非常原始的依赖项容器类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的代码和问题:

  • 不要对容器(或其他任何内容(使用静态类或单例;它们都是邪恶的。看看疙瘩代替。
  • 确定是希望mysqliWrapper扩展mysql还是依赖于它。
  • 通过从mysqliWrapper内部调用IoC,可以将一个依赖项交换为另一个依赖项。您的对象不应知道或使用容器;否则它不再是 DIC,而是服务定位器(反(模式。
  • 容器中注册类文件之前,无需require类文件,因为您根本不知道是否要使用该类的对象。在一个地方完成所有容器设置。如果不使用自动加载器,则可以在向容器注册的匿名函数中require

其他资源:

  • 控制容器的反转和依赖注入模式,作者:Martin Fowler
  • 不要找东西——一个干净的代码 谈谈 IoC/DI