[关闭]
@snowdiva 2016-10-17T02:27:55.000000Z 字数 4406 阅读 472

[ Laravel 5.3 学习 ] - 理解laravel的依赖注入与控制反转

Laravel学习 依赖注入 控制反转

原文地址:Laravel China



引言

依赖注入与控制反转

依赖注入 当我第一次接触这个词的时候,我是有些丈二和尚摸不着头脑的,至今我也很感到比较困惑的,所以今天我们探索一下Laravel中的依赖注入(dependency injection),来好好的理解它。控制反转 第一印象是好深奥的名词...看上去好像是说反转控制?不懂?那就理顺之!

起点

什么是依赖

没有你,我就活不下去,那么,你就是我的依赖。说白了就是:

不是我自身的,却是我需要的,都是我所依赖的。一切需要外部提供的,都是需要进行依赖注入的。

我们用代码来描述一下:

  1. class Boy
  2. {
  3. protected $girl;
  4. public function __construct(Girl $girl)
  5. {
  6. $this->girl = $girl;
  7. }
  8. }
  9. class Girl
  10. {
  11. // code...
  12. }
  13. $boy = new Boy(); // Error: Boy must have girlfriend!
  14. // so 必须要给他一个女朋友才行
  15. $girl = new Girl();
  16. $boy = new Boy($girl); // Right! So Happy!

从上面代码我们可以看到Boy强依赖Girl必须在构造时注入Girl的实例才行。

那么为什么要有依赖注入这个概念,依赖注入到底解决了什么问题?

我们将上述代码修正一下,改成我们初学时写过的代码:

  1. class Boy
  2. {
  3. protected $gril;
  4. public function __construct()
  5. {
  6. $this->girl = new Girl();
  7. }
  8. }

这种方式与前面的方式有什么不同呢?

我们会发现Boy的女朋友被我们硬编码到Boy的身体里去了...每次Boy重生自己想换个类型的女朋友都要吧自己扒光才行...(⊙o⊙)哦~

某天Boy特别喜欢一个LoliGirl,非常想让她做自己的女朋友...怎么办?重生自己~扒光自己~把Girl扔了~把LoliGirl塞进去!

  1. class LoliGirl
  2. {
  3. // code...
  4. }
  5. class Boy
  6. {
  7. protected $gril;
  8. public function __construct()
  9. {
  10. // $this->girl = new Girl(); // I'm so sorry...
  11. $this->girl = new LoliGirl();
  12. }
  13. }

某天Boy迷恋上了御姐...(⊙o⊙)...Boy好烦...

是不是感觉不太好?每次遇到真心相待的人却要这么的折磨自己?(这不是R0么!菜月昴,你还好么???)

Boy说,我要变得强大一点。我不想被改来改去!

好吧,让我们让Boy强大一点:

  1. interface Girl
  2. {
  3. // Boy need knows that I have some abilities.
  4. }
  5. class LoliGirl implement Girl
  6. {
  7. // I will implement Girl's abilities.
  8. }
  9. class Vixen implement Girl
  10. {
  11. // Vixen definitely is a girl, do not doubt it.
  12. }
  13. class Boy
  14. {
  15. protected $girl;
  16. public function __construct(Girl $girl)
  17. {
  18. $this->girl = $girl;
  19. }
  20. }
  21. $loliGirl = new LoliGirl();
  22. $vixen = new Vixen();
  23. $boy = new Boy($loliGirl);
  24. $boy = new Boy($vixen);

Boy很高兴,终于可以不用扒开自己就可以体验不同的人生了...So Happy!

小结

因为大多数应用程序都是由两个或者更多的类通过彼此合作来实现业务逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要考自己实现,那么将导致代码高度耦合并且难以维护和调试。

所以才有了依赖注入的概念,依赖注入解决了以下问题:

=。=前面的依赖注入居然需要我们手动的去注入依赖,作为程序员的我们怎么可以容忍这种低效的诸如方式,好吧,我们先来了解一下IOC的概念。

控制反转(Inversion Of Control,IOC)

控制反转 是面对对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injuction,DI),还有一种叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,有一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

也就是说,我们需要一个调控系统,这个调控系统中我们存放一些对象的实体,或者对象的描述。在对象创建的时候将对象所依赖的对象的引用传递过去。在Lravel中Service Container就是这个高效的调控系统,它是Laravel的核心。下面我们看一下Laravel是如何实现自动依赖注入的。

Laravel 中的依赖注入

现在我们看文档给的例子应该不难理解了:

  1. <?php
  2. namespace App\Jobs;
  3. use App\User;
  4. use Illuminate\Contracts\Mail\Mailer;
  5. use Illuminate\Contracts\Bus\SelfHandling;
  6. class PurchasePodcast implements SelfHandling
  7. {
  8. /**
  9. * The mailer implementation.
  10. */
  11. protected $mailder;
  12. /**
  13. * Create a new instance.
  14. *
  15. * @param Mailer $mailer
  16. * @return void
  17. */
  18. public function __construct(Mailer $mailer)
  19. {
  20. $this->mailer = $mailer;
  21. }
  22. /**
  23. * Purchase a podcast.
  24. *
  25. * @return void
  26. */
  27. public function handle()
  28. {
  29. // code...
  30. }
  31. }

在这个例子中,PurchasePodcast类工作的时候需要进行电子邮件的操作,因此,我们将注入一种服务,让它可以发送电子邮件。由于服务的注入我们可以轻易的直接调用邮件发送类的实例。当然,我们也可以轻松地进行"mock"(模拟),或测试我们的应用程序时候,轻松的创建一个测试邮件。

说到 Laravel 中的依赖注入,我们不得不了解一下Laravel中的Service Container(服务容器)。

服务容器(Service Container)

Laravel服务容器是管理类的依赖关系,并执行依赖注入的强大的工具。依赖注入是一种这样的非常奇特的语法:类依赖关系通过构造器注入类中,或在某种情况下通过setter方法进行注入。

从介绍不难看出服务容器就是控制反转的容器,它就是前文说到的调度系统。实现依赖注入的方式可以是在构造函数中或者setter方法中。

如果我们仔细研究了Service Container我们就会发现 Laravel 的服务容器中只有存储了对象的描述,而并不需要知道如何具体的去构造一个对象,因为他会更具php的反射服务区自动解析具体化一个对象。

反射

在计算机科学中,反射是指计算机在运行时(Run time)可以访问、检测和修改它自身状态或行为的一种能力。用来比喻说,那种程序能够“观察”并且修改自己的行为。

  • 作为一个第一类对象发现并修改源代码的结构(如代码块、类、方法、协议等)。
  • 将跟class或function匹配的转换成class或function的调用或引用。
  • 在运行时像对待源代码豫剧一样计算字符串。
  • 创建一个新的语言字节码解释器来给编程结构一个新的意义或用途。

PHP实现的反射可以在官网文档中进行查看:反射API

示例

  1. $reflector = new ReflectionClass('App\User');
  2. if ($reflector->isInstantiable()) {
  3. $user = $refector->newInstance(); // in other case you can send any arguments.
  4. }

Laravel 的服务容器的build方法中需要通过反射服务来解析依赖关系,比如说construct函数中需要传递的依赖参数有哪些?它就需要用到如下方法:

  1. $constructor = $reflector->getConstructor();
  2. // if there are no constructores, that means there are no dependencies then
  3. // we can just resolve the instances of the objects right away, without
  4. // resolving any other types or dependencies out of these containers.
  5. if (is_null($constructor)) {
  6. array_pop($this->buildStack);
  7. return new $concrete;
  8. }
  9. $dependencies = $constructor->getParameters();

现在我们应该对 Laravel 如何实现依赖的自动注入有点想法了吧?来整理一下疑问:

其实...不是这样的...

很多时候我们为了提高代码的扩展性和维护性,在编写类时依赖的是接口或抽象类,而并不是一个具体的实现类。明白了吗?依赖解析的时候如果只解析到接口或抽象类,然后利用反射,那么这个依赖肯定是错误的。

那么我们就需要在调度系统中注入相关依赖的映射关系,然后在需要的时候正确的解析关系。比如说,喂,我需要一个A,你别给我B啊。

  1. $container->bind('a', function() {
  2. return new B(); // just this for you.
  3. });
  4. $a = $container->make('a');

总结

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注