快进:
我写这篇文章的目的是为了更好地理解依赖项注入和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术语表达这一点,请使用强制汽车类比来考虑以下示例:
class Car {
public function start() {
$engine = new Engine();
$engine->vroom();
}
}
Car取决于Engine。Engine是Car的依赖关系。不过,这段代码相当糟糕,因为:
这种依赖是隐式的;在检查Car的代码之前,您不知道它在那里。
这些类是紧密耦合的;不能用MockEngine代替Engine进行测试,也不能用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);
上面是一个构造函数注入的例子,其中依赖项(依赖对象)通过类构造函数提供给依赖项(使用者)。另一种方法是在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'));
});
注意,我们告诉容器从自身抓取EDOCX1的一个实例(15),以组装EDOCX1的一个实例(14)。然后,为了获得一个自动注入依赖项的Database实例,我们只需:
$database = $dic->resolve('database');
这就是重点。一个稍微复杂一些,但仍然相对简单和容易掌握的php di/ioc容器是疙瘩。查看其文档以获取更多示例。
关于操作规程和问题:
不要将静态类或单例用于容器(或其他任何内容);它们都是邪恶的。检查丘疹代替。
决定你是想要你的mysqliWrapper类扩展mysql还是依赖它。
通过从mysqliWrapper内部调用IoC,您将从一个依赖项交换到另一个依赖项。您的对象不应该知道或使用容器;否则它不再是dic,而是服务定位器(anti)模式。
在容器中注册类文件之前,您不需要require,因为您根本不知道是否要使用该类的对象。在一个地方设置所有容器。如果不使用自动加载程序,则可以在向容器注册的匿名函数内执行ecx1(26)。
其他资源:
Martin Fowler的控制容器倒置和依赖注入模式
不要找东西--一个关于IOC/DI的干净代码讨论
在这个例子中,您创建的工厂看起来像是一个美化的关联数组(这个$dic变量)。疙瘩是一个美化的关联数组。调用$dic->resolve()是一个服务定位器,它会欺骗对象API。你阅读福勒和干净的代码谈话-但提供丘疹和反模式作为解决方案。我想你需要重新考虑一下关于$database =的部分…否则,很高兴看到有人声明不使用statics:-)
@Jimbo它只是一个服务定位器,如果你把它作为一个整体使用,也就是说,你把它传递给你的对象,这是我在回答中明确警告过的。不过,使用类似的简单容器在靠近应用程序入口点的某个位置构建对象图并没有"反模式"的地方。顺便说一句,在一种语言中,有很多东西可以成长为"美化的关联数组",其中关联数组/散列映射是极少数非标量核心类型之一;-)
哈哈,说得对。但是,$database = $dic->resolve('database');和服务定位器的警告似乎没有联系。我没想到有人警告我要用这个。如果你对这类东西感兴趣,让我来打消你的念头:链接。
谢谢,真的帮了我。
很好的回答,感谢你花时间来解释-真的有助于理解应用程序设计的大局等!(与此无关,你的$engine->vroom();拖拽了一段非常古老的记忆,让我想起了阿塔里街上的弗鲁姆,上帝,我喜欢这个游戏,声音在今天是一种现象,无论如何,回到工作中…)