了解IoC容器和dependency injection

快速前进:

我写这个的目的是为了更好地理解dependency injection和IoC容器,也是为了以后我可以纠正它的错误,并用它来帮助我的几个朋友教他们。

到目前为止,我已经尝试阅读各种框架(laravel,fuel,codeigniter,symfony)的文档,我发现框架有太多不同的方面,我需要使用它,我决定尝试在尝试在框架中使用它们之前,先自己学习每个主要部分。

我花了几个小时search各种各样的意思,看着stackoverflow响应,并阅读各种文章试图了解什么是IoC,以及如何使用它来正确pipe理依赖关系,我相信我明白它在概念上是什么,但我仍然灰色关于如何正确实施。 我认为读这本书帮助我的最好方法是给出我目前对IoC容器和dependency injection的理解,然后让那些比我更好理解的人指出我的理解力不足。

我的理解:

  • 依赖关系是当ClassA的实例需要ClassB的实例来实例化ClassA的新实例时。
  • dependency injection是当ClassA通过ClassB的构造函数中的参数或通过set〜DependencyNameHere〜(〜DependencyNameHere〜$ param)函数传递ClassB的实例时。 (这是我不完全确定的领域之一)
  • 一个IoC容器是一个单例类(在任何给定的时间只能有一个实例实例化),在这里可以注册实例化这个类的对象的具体方法。 下面是我想要描述的一个例子的链接,以及我一直使用的IoC容器的类定义

所以在这一点上,我开始尝试使用IoC容器更复杂的情况。 到目前为止,似乎为了使用IoC容器,我仅限于几乎所有我想创build的具有IoC容器中定义的依赖关系的类的关系。 如果我想创build一个inheritance一个类的类,但是只有在以IoC容器中注册的特定方式创build父类的情况下。

例如:我想创build一个mysqli的子类,但是我想在IoC容器中注册这个类,只用我之前在IoC容器中注册的方式实例化父类。 我想不出没有重复代码的方法(因为这是一个学习项目,我试图尽量保持它的“纯”)。 以下是我想描述的更多的例子。

所以这里是我的一些问题:

  • 我想尽量做到不违反OOP原则? 我知道在c + +我可以使用dynamic内存和复制构造函数来完成它,但我一直无法在PHP中find这种function。 (我会承认,除了__construct之外,使用其他任何魔术方法的经验都很less,但是如果我理解正确的话,可以从阅读和__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 取决于 EngineEngineCar依赖 。 这段代码虽然很糟糕,因为:

  • 依赖是隐含的; 直到你检查Car的代码,你才知道它在那里
  • 这些课程紧密结合在一起; 你不能用MockEngine EngineMockEnginetesting目的,也不TurboEngine没有修改Car扩展原始EngineMockEngine代替。
  • 对于一辆汽车来说,能够为自己搭build一个引擎,看起来有点傻,不是吗?

dependency injection是一种解决所有这些问题的方法,通过使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注入 ,主要用于在运行时应该交换的依赖关系。

任何不平凡的项目都是由一堆相互依存的组件组成的,很容易就会很快失去注入的内容。 dependency injection容器是知道如何实例化和configuration其他对象的对象,知道它们与项目中其他对象之间的关系,并为你进行dependency injection。 这使您可以集中pipe理所有项目(inter)依赖关系,更重要的是,可以更改/模拟其中的一个或多个项目,而无需编辑代码中的一堆地方。

让我们抛开汽车的比喻,看一下OP想要达到的目标。 比方说,我们有一个Database对象取决于mysqli对象。 比方说,我们想要使用一个非常原始的依赖性压缩容器类DIC ,它公开了两个方法: register($name, $callback)注册一个创build给定名称下的对象的方法,并使用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 的dependency injection模式
  • 不要找东西 – 关于IoC / DI的Clean Code Talk
Interesting Posts