64

我的理解:

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

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

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

所以这是我的一些问题:

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

1 回答 1

134

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

class Car {

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

}

Car 取决于Engine。_ EngineisCar依赖。这段代码很糟糕,因为:

  • 依赖是隐含的;Car在您检查's 代码之前,您不知道它的存在
  • 类是紧密耦合的;您不能将 替换为Engine用于MockEngine测试目的,或者TurboEngine在不修改Car.
  • 一辆汽车能够为自己制造引擎看起来有点傻,不是吗?

依赖注入是一种解决所有这些问题的方法,方法是明确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);

上面是一个构造函数注入的例子,其中依赖(被依赖的对象)通过类构造函数提供给依赖(消费者)。setEngine另一种方法是在类中公开一个方法Car并使用它来注入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的代码和问题:

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

其他资源:

于 2013-09-02T00:44:53.930 回答