关于php:了解IoC容器和依赖注入

Understanding IoC Containers and Dependency Injection

快进:

我写这篇文章的目的是为了更好地理解依赖项注入和IOC容器,同时也为了以后能够纠正其中的错误,并用它来帮助我的一些朋友了解它们。

到目前为止,我已经尝试阅读各种框架的文档(Laravel、Fuel、CodeIgniter、Symfony),我发现框架有太多不同的方面,我需要感到舒适地使用它,所以我决定在尝试在F中使用它们之前,尝试单独学习每个主要部分。拉美工自己。

我花了好几个小时搜索各种含义,查看StackOverflow响应,阅读各种文章,试图了解IOC是什么以及如何使用它来正确管理依赖关系,我相信我理解它的概念,但我仍然对如何正确地实现它感到灰暗。我认为任何人读到这篇文章来帮助我的最好方法就是给出我目前对IOC容器和依赖注入的理解,然后让那些比我有更好理解的人指出我的理解不足之处。

我的理解:

  • 依赖性是指类A的实例需要类B的实例来实例化类A的新实例。
  • 依赖项注入是当类A通过类A的构造函数中的参数或通过集合~dependencyNamehere~(~dependencyNamehere~$param)函数传递类B的实例时进行的。(这是我不完全确定的领域之一)。
  • IOC容器是一个单实例类(在任何给定时间只能有一个实例),在该类中,可以注册为该项目实例化这些类对象的特定方法。下面是一个链接,指向我试图描述的示例,以及我一直使用的IOC容器的类定义。

所以在这一点上,我开始尝试在更复杂的场景中使用IOC容器。到目前为止,似乎为了使用IOC容器,我被限制在HAS关系中,对于我要创建的几乎所有类,它都要在IOC容器中定义依赖关系。如果我想创建一个继承类的类,但是父类是以特定的方式创建的,并且它是在IOC容器中注册的,那该怎么办?

例如:我想创建一个mysqli的子类,但是我想在ioc容器中注册这个类,以便仅用以前在ioc容器中注册的方式构造的父类进行实例化。我想不出一种不复制代码就能做到这一点的方法(因为这是一个学习项目,所以我尽量保持它的"纯粹")。下面是我想描述的更多例子。

下面是我的一些问题:

  • 在不违反OOP原则的情况下,我在上面所做的是可行的吗?我知道在C++中,我可以使用动态内存和复制构造函数来完成它,但是在PHP中我还没有找到那种功能。(我承认除了uu construct之外,我几乎没有使用其他任何魔法方法的经验,但是从阅读和u clone开始,如果我理解正确,我就不能在构造函数中使用它来让子类被实例化为父类实例的克隆)。
  • 关于国际奥委会,我所有的依赖类定义应该放在哪里?(我的ioc.php应该只在顶部有一堆Require_Once(‘dependencyClassDefinition.php’)吗?我的直觉反应是有更好的方法,但我还没有想出一个)
  • 我应该在哪个文件中注册我的对象?目前,在类定义之后调用ioc.php文件中的ioc::register()。
  • 在注册需要依赖关系的类之前,我是否需要在IOC中注册依赖关系?因为在实际实例化在IOC中注册的对象之前,我不会调用匿名函数,所以我想不会,但这仍然是一个问题。
  • 还有什么我忽视的,我应该做或使用的吗?我试图一步一步地完成它,但我也不想知道我的代码是可重用的,最重要的是,对我的项目一无所知的人可以阅读和理解它。

我知道这是非常漫长的,我只是想提前感谢任何花时间阅读它的人,更重要的是,感谢任何人分享他们的知识。


简单地说(因为这不是一个仅限于OOP世界的问题),依赖性是组件A需要(依赖)组件B来完成它应该做的事情的情况。在这个场景中,这个词还用于描述依赖组件。要用oop/php术语表达这一点,请使用强制汽车类比来考虑以下示例:

1
2
3
4
5
6
7
8
class Car {

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

}

Car取决于EngineEngineCar的依赖关系。不过,这段代码相当糟糕,因为:

  • 这种依赖是隐式的;在检查Car的代码之前,您不知道它在那里。
  • 这些类是紧密耦合的;不能用MockEngine代替Engine进行测试,也不能用TurboEngine扩展原始类而不修改Car
  • 汽车能自己制造发动机,这看起来有点傻,不是吗?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)从该名称中获取对象。我们的容器设置如下所示:

1
2
3
4
5
6
7
$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'));
});

注意,我们告诉容器从自身抓取EDOCX1的一个实例(15),以组装EDOCX1的一个实例(14)。然后,为了获得一个自动注入依赖项的Database实例,我们只需:

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

这就是重点。一个稍微复杂一些,但仍然相对简单和容易掌握的php di/ioc容器是疙瘩。查看其文档以获取更多示例。

关于操作规程和问题:

  • 不要将静态类或单例用于容器(或其他任何内容);它们都是邪恶的。检查丘疹代替。
  • 决定你是想要你的mysqliWrapper类扩展mysql还是依赖它。
  • 通过从mysqliWrapper内部调用IoC,您将从一个依赖项交换到另一个依赖项。您的对象不应该知道或使用容器;否则它不再是dic,而是服务定位器(anti)模式。
  • 在容器中注册类文件之前,您不需要require,因为您根本不知道是否要使用该类的对象。在一个地方设置所有容器。如果不使用自动加载程序,则可以在向容器注册的匿名函数内执行ecx1(26)。

其他资源:

  • Martin Fowler的控制容器倒置和依赖注入模式
  • 不要找东西--一个关于IOC/DI的干净代码讨论