了解 IoC 容器和依赖注入 [英] Understanding IoC Containers and Dependency Injection

查看:40
本文介绍了了解 IoC 容器和依赖注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的理解:

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

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

所以例如:我想创建一个mysqli的子类,但是我想在IoC容器中注册这个类,只用我之前在IoC容器中注册的方式构造的父类进行实例化.我想不出不复制代码的方法(因为这是一个学习项目,我试图让它尽可能纯").以下是我要描述的更多示例.>

以下是我的一些问题:

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

解决方案

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

class Car {公共函数开始(){$engine = 新引擎();$engine->vroom();}}

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

  • 依赖是隐含的;你不知道它在那里,直到你检查 Car 的代码
  • 这些类是紧密耦合的;您不能将 Engine 替换为 MockEngine 用于测试目的或 TurboEngine 在不修改 Car>.
  • 汽车能够为自己制造引擎看起来有点傻,不是吗?

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

class Car {受保护的 $engine;公共函数 __construct(Engine $engine) {$this->engine = $engine;}公共函数开始(){$this->engine->vroom();}}$engine = new SuperDuperTurboEnginePlus();//引擎的子类$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 的代码和问题:

  • 不要为你的容器(或其他任何事情)使用静态类或单例;他们都是邪恶的.试试 Pimple.
  • 决定你的 mysqliWrapper 类是 extend mysql 还是 depend 依赖于它.
  • 通过从 mysqliWrapper 中调用 IoC,您将一个依赖项交换为另一个依赖项.你的对象不应该知道或使用容器;否则它不再是 DIC,而是服务定位器(反)模式.
  • 在将类文件注册到容器中之前,您不需要 require 类文件,因为您根本不知道是否要使用该类的对象.在一个地方完成所有容器设置.如果您不使用自动加载器,则可以在向容器注册的匿名函数中require.
<小时>

其他资源:

My understanding:

  • A dependency is when an instance of ClassA requires an instance of ClassB to instantiate a new instance of ClassA.
  • A dependency injection is when ClassA is passed an instance of ClassB, either through a parameter in ClassA's constructor or through a set~DependencyNameHere~(~DependencyNameHere~ $param) function. (This is one of the areas I'm not completely certain on).
  • An IoC container is a singleton Class(can only have 1 instance instantiated at any given time) where the specific way of instantiating objects of those class for this project can be registered. Here's a link to an example of what I'm trying to describe along with the class definition for the IoC container I've been using

So at this point is where I start trying use the IoC container for more complicated scenarios. As of now it seems in order to use the IoC container, I am limited to a has-a relationship for pretty much any class I want to create that has dependencies it wants to define in the IoC container. What if I want to create a class that inherits a class, but only if the parent class has been created in a specific way it was registered in the IoC container.

So for example: I want to create a child class of mysqli, but I want to register this class in the IoC container to only instantiate with the parent class constructed in a way I've previously registered in the IoC container. I cannot think of a way to do this without duplicating code (and since this is a learning project I'm trying to keep it as 'pure' as possible). Here are some more examples of what I am trying to describe.

So here are some of my questions:

  • Is what I'm trying to do above possible without breaking some principle of OOP? I know in c++ I could use dynamic memory and a copy constructor to accomplish it, but I haven't been able to find that sort of functionality in php. (I will admit that I have very little experience using any of the other magic methods besides __construct, but from reading and __clone if I understood correctly, I couldn't use in the constructor it to make the child class being instantiated a clone of an instance of the parent class).
  • Where should all my dependency class definitions go in relation to the IoC? (Should my IoC.php just have a bunch of require_once('dependencyClassDefinition.php') at the top? My gut reaction is that there is a better way, but I haven't come up with one yet)
  • What file should I be registering my objects in? Currently doing all the calls to IoC::register() in the IoC.php file after the class definition.
  • Do I need to register a dependency in the IoC before I register a class that needs that dependency? Since I'm not invoking the anonymous function until I actually instantiate an object registered in the IoC, I'm guessing not, but its still a concern.
  • Is there anything else I'm overlooking that I should be doing or using? I'm trying to take it one step at a time, but I also don't want to know that my code will be reusable and, most importantly, that somebody who knows nothing about my project can read it and understand it.

解决方案

Put simply (because it's not a problem limited to OOP world only), a dependency is a situation where component A needs (depends on) component B to do the stuff it's supposed to do. The word is also used to describe the depended-on component in this scenario. To put this in OOP/PHP terms, consider the following example with the obligatory car analogy:

class Car {

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

}

Car depends on Engine. Engine is Car's dependency. This piece of code is pretty bad though, because:

  • the dependency is implicit; you don't know it's there until you inspect the Car's code
  • the classes are tightly coupled; you can't substitute the Engine with MockEngine for testing purposes or TurboEngine that extends the original one without modifying the Car.
  • It looks kind of silly for a car to be able to build an engine for itself, doesn't it?

Dependency injection is a way of solving all these problems by making the fact that Car needs Engine explicit and explicitly providing it with one:

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);

The above is an example of constructor injection, in which the dependency (the depended-on object) is provided to the dependent (consumer) through the class constructor. Another way would be exposing a setEngine method in the Car class and using it to inject an instance of Engine. This is known as setter injection and is useful mostly for dependencies that are supposed to be swapped at run-time.

Any non-trivial project consists of a bunch of interdependent components and it gets easy to lose track on what gets injected where pretty quickly. A dependency injection container is an object that knows how to instantiate and configure other objects, knows what their relationship with other objects in the project are and does the dependency injection for you. This lets you centralize the management of all your project's (inter)dependencies and, more importantly, makes it possible to change/mock one or more of them without having to edit a bunch of places in your code.

Let's ditch the car analogy and look at what OP's trying to achieve as an example. Let's say we have a Database object depending on mysqli object. Let's say we want to use a really primitive dependency indection container class DIC that exposes two methods: register($name, $callback) to register a way of creating an object under the given name and resolve($name) to get the object from that name. Our container setup would look something like this:

$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'));
});

Notice we're telling our container to grab an instance of mysqli from itself to assemble an instance of Database. Then to get a Database instance with its dependency automatically injected, we would simply:

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

That's the gist of it. A somewhat more sophisticated but still relatively simple and easy to grasp PHP DI/IoC container is Pimple. Check its documentation for more examples.


Regarding OP's code and questions:

  • Don't use static class or a singleton for your container (or for anything else for that matter); they're both evil. Check out Pimple instead.
  • Decide whether you want your mysqliWrapper class extend mysql or depend on it.
  • By calling IoC from within mysqliWrapper you're swapping one dependency for another. Your objects shouldn't be aware of or use the container; otherwise it's not DIC anymore it's Service Locator (anti)pattern.
  • You don't need to require a class file before registering it in the container since you don't know if you're going to use an object of that class at all. Do all your container setup in one place. If you don't use an autoloader, you can require inside the anonymous function you register with the container.

Additional resources:

这篇关于了解 IoC 容器和依赖注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆