依赖注入和服务容器

一直想学习laravel源码,之前就在IDE中来回跳转,有种盲人摸象的感觉,前段时间看到了laravel的生命周期,才有了一点点思路,算是正式开启laravel的学习之路吧,今天先谈谈我对依赖注入的理解,如果有什么不对的地方请各位大佬帮忙指出。

依赖

  class PayOne{
      public function __constuct()
      {
          $this->method = new WechatOne();
      }
      public function payBill()
      {
          $this->method->pay();
      }
  }

  class WechatOne{
      public function __construct()
      {
          // 这里会对微信支付需要的一些配置做响应的设置
      }

      public function pay()
      {
          // 完成第三方接口的调用完成支付
          echo "WechatOne pay";
      }
  }

在Pay类的构造函数中实例化了Wechat类,也就是Pay依赖类Wechat,但是这样会导致如果想要换一种支付类型或者说Wechat类本身发生了修改就必须修改Pay类,为了避免修改Pay类我们就设想是否可以将Wechat类的实例化放到Pay类外部,然后通过某种方式直接把Wechat的实例化对象传递给Pay类这个就是我们接下来要说的依赖注入

依赖注入

 class PayTwo{
      public function __construct(WechatOne $wechat)
      {
          $this->method = $wechat;
      }
      
      public function payBill()
      {
          $this->method->pay();
      }
}
$wechat = new WechatOne;
$pay = new PayTwo($wechat);
$pay->payBill(); //WechatOne pay

在实例化Pay时需要传入一个Wechat的实例化对象,这个比上边一个例子灵活了一点,我们可以在外边实例化Wechat,不会因为Wechat改变而修改Pay,这个其实就是一种依赖注入了,也称为控制反转。但是依然没有办法灵活的变换支付类型,我们试想一下,如果所有的支付类型都拥有pay方法,我们想用哪个支付类型就在Pay初始化时传入哪个支付类型是不是更方便更灵活呢?

interface PayMethod{
    public function pay();
}

class WechatTwo implements PayMethod{
        public function __construct()
        {
            // 这里会对微信支付需要的一些配置做响应的设置
        }

        public function pay()
        {
            // 完成第三方接口的调用完成支付
            echo "WechatTwo pay";
        }
}

class Alipay implements PayMethod{
        public function __construct()
        {
            // 这里会对支付宝支付需要的一些配置做响应的设置
        }

        public function pay()
        {
            // 完成第三方接口的调用完成支付
            echo "Alipay pay";
        }
}

class PayThree{
    public function __construct( PayMethod $payMethod)
      {
          $this->method = $payMethod;
      }
      
      public function payBill()
      {
          $this->method->pay();
      }
}

$alipay = new Alipay;
$pay = new PayThree($alipay);
$pay->payBill(); //Alipay pay

在上边的例子中我们定义了一个支付方式的接口,所有的支付方式都需要实现这个接口,在Pay类实例化时我们只要注入一个实现了一个PayMethod接口的对象就可以,到此为止我们在不修改Pay类的前提下已经可以灵活变动支付类型或者修改某种支付类型的内部结构了,但是通过手工注入依赖的方式,在laravel中的服务容器就帮我们实现了自动注入。

服务容器

容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描述(类、接口)或者是提供对象的回调,通过这种容器,我们得以实现许多高级的功能,其中最常提到的,就是 “解耦” 、“依赖注入(DI)”

简单来说服务容器预先将对象“装”起来,在调用的时候实例化。

  class Container{
        protected $bindings = [];
  
        // 用来绑定服务
        public function bind($abstrct, $concrete = null, $shared = fasle)
        {
            //shared 是用来表示是否为单例,如果是单例会在$instance中保存一份,这里不做解释
            
            if (is_null($concrete)) {
                $concret = abstrct;
            }
            if (!$concrete instanceof Closure) { //如果不是回调函数则,生成一个回调函数
                $concrete = $this->getClosure($abstract, $concrete);
            }

          $this->bindings[$abstract] = compact('concrete', 'shared');
        }

        protected function getClosure($abstract, $concrete)
        {
            //这里的回调函数是实现调用时才实例化的关键
              return function($container) use($abstract, $concrete) {
                  $method = ($abstract == $concrete) ? 'build' : 'make';
                  return $container->$method($concrete);
              } 
        }

        public function build($conrete)
        {
            if ($conrete instanceof Closure) {
                return $conrete($this); // 这里的this就是getClosure中的container
            }
          
            $reflector = new ReflectionClass($conrete);   //这里的反射是实现自动注入的关键
            
            $constructor = $reflector->getConstructor();

            //如果为null,说明此类使用的是系统默认构造函数,直接new就可以
            if(is_null($constructor)) {
                  return new $conrete;
            }
            $paramters = $constructor->getParamters();
            $instances = $this->getDependencies($paramters);
            return $reflector->newInstanceArgs($instances);
      }


      protected function getDependencies($paramters)\
      {
          $dependencies = [];
            foreach ($paramters as $paramter) {
                $dependency = $paramter->getClass();

                if (is_null($dependency)) { //如果参数是简单的数据类型,则会获取他们的默认值传入,这里不做介绍
                    $dependencies[] = $this->resoveUnClass(paramter);
                } else {
                    $dependencies[] = $this->resolveClass($paramter);
                }
            }
      }

    protected function resoveClass(ReflectionParameter $parameter)
    {
          $this->make($parameter->getClass()->name);
    }

    public function make($abstract)
    {
          $concrete = $this->getConcrete($abstract);

          if ($this->isBuildable($concrete, $abstract)) {
              $object = $this->build($concrete);
          } else {
              $object = $this->make($concrete);
          }
          return $object;
    }


    protected function getConcrete($abstract)
    {
        if (isset($this->bindings[$abstract])) {
            return $abstract;
        }
        return $this->bindings[$abstract];
    }


    protected function isBuildable($concrete, $abstract)
    {
        return $concrete === $abstract || $concrete instanceof Closure;
    } 
}

$container = new Container;
$payTwo = $container->make('PayTwo');   //这个之前不需要先绑定,因为PayTwo这个实例化的时候需要的是一个确定的对象,这个的执行路线为getConcrete获取到$concrete = 'PayTwo'和$abstract相等,就执行build,$concrete是个字符串不是回调函数,继续走到获取到反射类,构造函数为WechatOne,执行resolveClass,启动一个$container->make('WechatOne'),这个构造函数为null,直接new WechatOne,并将结果传递。
$payTwo->pay(); //WechatOne pay

$container->bind('PayMethod', 'WechatTwo');
$payThree = $container->make('PayThree'); 
$payThree->pay(); //WechatTwo 

这里容器类的代码是出自[Laravel框架关键技术解析] (),是源码的简化版但是思路一致。

最后

我在运行的时候,使用了phpstrom的断点调试,感觉一步步理解会更加深刻,希望这是一篇有点作用的文章,参考了很多文章结合自己的理解写了这第一篇文章,加油!!!

你可能感兴趣的:(依赖注入和服务容器)