zoukankan      html  css  js  c++  java
  • 如何理解laravel 的 IOC 容器

    1.依赖

    IOC( inversion of controller )叫做控制反转模式,也可以称为(dependency injection ) 依赖注入模式。要理解依赖注入的概念我们先理解下什么依赖

     1 //支付宝支付
     2 class Alipay {
     3       public function __construct(){}
     4 
     5       public function pay()
     6       {
     7           echo 'pay bill by alipay';
     8       }
     9 }
    10 //微信支付
    11 class Wechatpay {
    12       public function __construct(){}
    13 
    14       public function pay()
    15       {
    16           echo 'pay bill by wechatpay';
    17       }
    18 }
    19 //银联支付
    20 class Unionpay{
    21       public function __construct(){}
    22 
    23       public function pay()
    24       {
    25           echo 'pay bill by unionpay';
    26       }
    27 }
    28 
    29 //支付账单
    30 class PayBill {
    31 
    32       private $payMethod33 
    34       public function __construct( )
    35       {
    36           $this->payMethod= new Alipay ();
    37       }
    38 
    39       public function  payMyBill()
    40       {
    41            $this->payMethod->pay();
    42       }
    43 }
    44 
    45 
    46 $pb = new PayBill ();
    47 $pb->payMyBill();

    通过上面的代码我们知道,当我们创建一个class PayBill 的实例的时候, PayBill的构造函数里面有{ $this->payMethod= new Alipay (); }, 也就是实例化了一个class Alipay . 这个时候依赖就产生了, 这里可以理解为当我想用支付宝支付的时候, 那我首先要获取到一个支付宝的实例,或者理解为获取支付宝的功能支持. 当用我们完 new 关键字的时候, 依赖其实已经解决了,因为我们获取了Alipay 的实例.

    其实在我知道ioc概念之前,我的代码中大部分都是这种模式 ~ _ ~ . 这种有什么问题呢, 简单来说, 比如当我想用的不是支付宝而是微信的时候怎么办, 你能做的就是修改Payment 的构造函数的代码,实例化一个微信支付Wechatpay.

    如果我们的程序不是很大的时候可能还感觉不出什么,但是当你的代码非常复杂,庞大的时候,如果我们的需求经常改变,那么修改代码就变的非常麻烦了。所以ioc 的思想就是不要在 class Payment 里面用new 的方式去实例化解决依赖, 而且转为由外部来负责,简单一点就是内部没有new 的这个步骤,通过依赖注入的方式同样的能获取到支付的实例.

    2.依赖注入

    依赖我们知道了是什么意思,那依赖注入又是什么意思呢,我们把上面的代码拓展一下

     1 //支付类接口
     2 interface Pay
     3 {
     4     public function pay();
     5 }
     6 
     7 
     8 //支付宝支付
     9 class Alipay implements Pay {
    10       public function __construct(){}
    11 
    12       public function pay()
    13       {
    14           echo 'pay bill by alipay';
    15       }
    16 }
    17 //微信支付
    18 class Wechatpay implements Pay  {
    19       public function __construct(){}
    20 
    21       public function pay()
    22       {
    23           echo 'pay bill by wechatpay';
    24       }
    25 }
    26 //银联支付
    27 class Unionpay implements Pay  {
    28       public function __construct(){}
    29 
    30       public function pay()
    31       {
    32           echo 'pay bill by unionpay';
    33       }
    34 }
    35 
    36 //付款
    37 class PayBill {
    38 
    39       private $payMethod;
    40 
    41       public function __construct( Pay $payMethod)
    42       {
    43           $this->payMethod= $payMethod;
    44       }
    45 
    46       public function  payMyBill()
    47       {
    48            $this->payMethod->pay();
    49       }
    50 }
    51 
    52 //生成依赖
    53 $payMethod =  new Alipay();
    54 //注入依赖
    55 $pb = new PayBill( $payMethod );
    56 $pb->payMyBill();

    上面的代码中,跟之前的比较的话,我们加入一个Pay 接口, 然后所有的支付方式都继承了这个接口并且实现了pay 这个功能. 可能大家会问为什么要用接口,这个我们稍后会讲到.

    当我们实例化PayBill的之前, 我们首先是实例化了一个Alipay,这个步骤就是生成了依赖了,然后我们需要把这个依赖注入到PayBill 的实例当中,通过代码我们可以看到 { $pb = new PayBill( payMethod ); }, 我们是通过了构造函数把这个依赖注入了PayBill 里面. 这样一来 $pb 这个PayBill 的实例就有了支付宝支付的能力了.

    把class Alipay 的实例通过constructor注入的方式去实例化一个 class PayBill. 在这里我们的注入是手动注入, 不是自动的. 而Laravel 框架实现则是自动注入.

    3.反射

    在介绍IOC 的容器之前我们先来理解下反射的概念(reflection),因为IOC 容器也是要通过反射来实现的.从网上抄了一段来解释反射是什么意思

    "反射它指在PHP运行状态中,扩展分析PHP程序,导出或提取出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取的信息以及动态调用对象的方法的功能称为反射API。反射是操纵面向对象范型中元模型的API,其功能十分强大,可帮助我们构建复杂,可扩展的应用。其用途如:自动加载插件,自动生成文档,甚至可用来扩充PHP语言"

    举个简单的例子

     1 class B{
     2 
     3 }
     4 
     5 
     6 class A {
     7 
     8     public function __construct(B $args)
     9     {
    10     }
    11 
    12     public function dosomething()
    13     {
    14         echo 'Hello world';
    15     }
    16 }
    17 
    18 //建立class A 的反射
    19 $reflection = new ReflectionClass('A');
    20 
    21 $b = new B();
    22 
    23 //获取class A 的实例
    24 $instance = $reflection ->newInstanceArgs( [ $b ]);
    25 
    26 $instance->dosomething(); //输出 ‘Hellow World’
    27 
    28 $constructor = $reflection->getConstructor();//获取class A 的构造函数
    29 
    30 $dependencies = $constructor->getParameters();//获取class A 的依赖类
    31 
    32 dump($constructor);
    33 
    34 dump($dependencies);
    35 dump 的得到的$constructor$dependencies 結果如下
    36 //constructor
    37 ReflectionMethod {#351 
    38         +name: "__construct" 
    39         +class: "A" 
    40         parameters: array:1 [] 
    41         extra: array:3 [] 
    42         modifiers: "public"
    43 }
    44 
    45 //$dependencies
    46 array:1 [
    47         0 => ReflectionParameter {#352 
    48          +name: "args"
    49           position: 0
    50           typeHint: "B"
    51       }
    52 ]

    通过上面的代码我们可以获取到 class A 的构造函数,还有构造函数依赖的类,这个地方我们依赖一个名字为 'args' 的量,而且通过TypeHint可以知道他是类型为 Class B; 反射机制可以让我去解析一个类,能过获取一个类里面的属性,方法 ,构造函数, 构造函数需要的参数。 有个了这个才能实现Laravel 的IOC 容器.

    4.IOC容器

    接下来介绍一下Laravel 的IOC服务容器概念. 在laravel框架中, 服务容器是整个laravel的核心,它提供了整个系统功能及服务的配置, 调用. 容器按照字面上的理解就是装东西的东西,比如冰箱, 当我们需要冰箱里面的东西的时候直接从里面拿就行了. 服务容器也可以这样理解, 当程序开始运行的时候,我们把我们需要的一些服务放到或者注册到(bind)到容器里面,当我需要的时候直接取出来(make)就行了. 上面提到的 bind 和 make 就是注册 和 取出的 两个动作.

    5. IOC 容器代码

    好了,说了这么多,下面要上一段容器的代码了. 下面这段代码不是laravel 的源码, 而是来自一本书《laravel 框架关键技术解析》. 这段代码很好的还原了laravel 的服务容器的核心思想. 代码有点长, 小伙伴们要耐心看. 当然小伙伴完全可以试着运行一下这段代码,然后调试一下,这样会更有助于理解.

     1 <?php 
     2 
     3 //容器类装实例或提供实例的回调函数
     4 class Container {
     5 
     6     //用于装提供实例的回调函数,真正的容器还会装实例等其他内容
     7     //从而实现单例等高级功能
     8     protected $bindings = [];
     9 
    10     //绑定接口和生成相应实例的回调函数
    11     public function bind($abstract, $concrete=null, $shared=false) {
    12         
    13         //如果提供的参数不是回调函数,则产生默认的回调函数
    14         if(!$concrete instanceof Closure) {
    15             $concrete = $this->getClosure($abstract, $concrete);
    16         }
    17         
    18         $this->bindings[$abstract] = compact('concrete', 'shared');
    19     }
    20 
    21     //默认生成实例的回调函数
    22     protected function getClosure($abstract, $concrete) {
    23         
    24         return function($c) use ($abstract, $concrete) {
    25             $method = ($abstract == $concrete) ? 'build' : 'make';
    26             return $c->$method($concrete);
    27         };
    28         
    29     }
    30 
    31     public function make($abstract) {
    32         $concrete = $this->getConcrete($abstract);
    33 
    34         if($this->isBuildable($concrete, $abstract)) {
    35             $object = $this->build($concrete);
    36         } else {
    37             $object = $this->make($concrete);
    38         }
    39         
    40         return $object;
    41     }
    42 
    43     protected function isBuildable($concrete, $abstract) {
    44         return $concrete === $abstract || $concrete instanceof Closure;
    45     }
    46 
    47     //获取绑定的回调函数
    48     protected function getConcrete($abstract) {
    49         if(!isset($this->bindings[$abstract])) {
    50             return $abstract;
    51         }
    52 
    53         return $this->bindings[$abstract]['concrete'];
    54     }
    55 
    56     //实例化对象
    57     public function build($concrete) {
    58 
    59         if($concrete instanceof Closure) {
    60             return $concrete($this);
    61         }
    62 
    63         $reflector = new ReflectionClass($concrete);
    64         if(!$reflector->isInstantiable()) {
    65             echo $message = "Target [$concrete] is not instantiable";
    66         }
    67 
    68         $constructor = $reflector->getConstructor();
    69         if(is_null($constructor)) {
    70             return new $concrete;
    71         }
    72 
    73         $dependencies = $constructor->getParameters();
    74         $instances = $this->getDependencies($dependencies);
    75 
    76         return $reflector->newInstanceArgs($instances);
    77     }
    78 
    79     //解决通过反射机制实例化对象时的依赖
    80     protected function getDependencies($parameters) {
    81         $dependencies = [];
    82         foreach($parameters as $parameter) {
    83             $dependency = $parameter->getClass();
    84             if(is_null($dependency)) {
    85                 $dependencies[] = NULL;
    86             } else {
    87                 $dependencies[] = $this->resolveClass($parameter);
    88             }
    89         }
    90 
    91         return (array)$dependencies;
    92     }
    93 
    94     protected function resolveClass(ReflectionParameter $parameter) {
    95         return $this->make($parameter->getClass()->name);
    96     }
    97 
    98 }

    上面的代码就生成了一个容器,下面是如何使用容器

    1 $app = new Container();
    2 $app->bind("Pay", "Alipay");//Pay 为接口, Alipay 是 class Alipay
    3 $app->bind("tryToPayMyBill", "PayBill"); //tryToPayMyBill可以当做是Class PayBill 的服务别名
    4 
    5 //通过字符解析,或得到了Class PayBill 的实例
    6 $paybill = $app->make("tryToPayMyBill"); 
    7 
    8 //因为之前已经把Pay 接口绑定为了 Alipay,所以调用pay 方法的话会显示 'pay bill by alipay '
    9 $paybill->payMyBill(); 

    当我们实例化一个Container得到 $app 后, 我们就可以向其中填充东西了

    $app->bind("Pay", "Alipay");
    $app->bind("tryToPayMyBill", "PayBill");  

    当执行完这两行绑定码后, $app 里面的属性 $bindings 就已经有了array 值,是啥样的呢,我们来看下

     1 array:2 [
     2  "AppHttpControllersPay" => array:2 [
     3      "concrete" => Closure {#355 
     4        class: "AppHttpControllersContainer" 
     5        this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
     6        parameters: array:1 [
     7          "$c" => []
     8        ] 
     9        use: array:2 [
    10          "$abstract" => "AppHttpControllersPay"
    11         "$concrete" => "AppHttpControllersAlipay"
    12        ] 
    13        file: "C:project	estappHttpControllersIOCController.php" line:       "119 to 122"
    14    } 
    15    "shared" => false 
    16  ]
    17 
    18 "tryToPayMyBill" => array:2 [
    19      "concrete" => Closure {#359 
    20          class: "AppHttpControllersContainer" 
    21          this:Container{[#354](http://127.0.0.4/ioc#sf-dump-254248394-ref2354) …} 
    22          parameters: array:1 [
    23                "$c" => []
    24          ] 
    25          use: array:2 [
    26                "$abstract" => "tryToPayMyBill" 
    27                "$concrete" => "AppHttpControllersPayBill"
    28          ] 
    29          file: "C:project	estappHttpControllersIOCController.php" line: "119 to 122"
    30    } 
    31      "shared" => false 
    32  ]
    33 ]

    当执行 $paybill = $app->make("tryToPayMyBill"); 的时候, 程序就会用make方法通过闭包函数的回调开始解析了.

    解析'tryToPayBill' 这个字符串, 程序通过闭包函数 和build方法会得到 'PayBill' 这个字符串,该字符串保存在$concrete 上. 这个是第一步. 然后程序还会以类似于递归方式 将$concrete 传入 build() 方法. 这个时候build里面就获取了$concrete = 'PayBill'. 这个时候反射就派上了用场, 大家有没有发现,PayBill 不就是 class PayBill 吗? 然后在通过反射的方法ReflectionClass('PayBill') 获取PayBill 的实例. 之后通过getConstructor(),和getParameters() 等方法知道了 Class PayBill 和 接口Pay 存在依赖

     1 //$constructor = $reflector->getConstructor();
     2 ReflectionMethod {#374 
     3     +name: "__construct" 
     4     +class: "AppHttpControllersPayBill" 
     5     parameters: array:1 [
     6           "$payMethod" => ReflectionParameter {#371 
     7               +name: "payMethod" 
     8               position: 0 typeHint: "AppHttpControllersPay"
     9           }
    10     ]
    11      extra: array:3 [
    12           "file" => "C:project	estappHttpControllersIOCController.php"
    13           "line" => "83 to 86" 
    14           "isUserDefined" => true 
    15       ] 
    16     modifiers: "public"
    17 }
    18 
    19 
    20 //$dependencies = $constructor->getParameters();
    21 array:1 [
    22     0 => ReflectionParameter {#370 
    23         +name: "payMethod" 
    24         position: 0 
    25         typeHint: "AppHttpControllersPay"
    26         }
    27 ]

    接着,我们知道了有'Pay'这个依赖之后呢,我们要做的就是解决这个依赖,通过 getDependencies($parameters), 和 resolveClass(ReflectionParameter $parameter) ,还有之前的绑定$app->bind("Pay", "Alipay"); 在build 一次的时候,通过 return new $concrete;到这里我们得到了这个Alipay 的实例

    1 if(is_null($constructor)) {
    2             return new $concrete;
    3         }

    到这里我们总算结局了这个依赖, 这个依赖的结果就是实例化了一个 Alipay. 到这里还没结束

    $instances = $this->getDependencies($dependencies);

    上面的$instances 数组只有一个element 那就是 Alipay 实例

     array:1 [0 =>Alipay
          {#380}
     ]

    最终通过 newInstanceArgs() 方法, 我们获取到了 PayBill 的实例。

     return $reflector->newInstanceArgs($instances);

    到这里整个流程就结束了, 我们通过 bind 方式绑定了一些依赖关系, 然后通过make 方法 获取到到我们想要的实例. 在make中有牵扯到了闭包函数,反射等概念.

    好了,当我们把容器的概念理解了之后,我们就可以理解下为什么要用接口这个问题了. 如果说我不想用支付宝支付,我要用微信支付怎么办,too easy.

    $app->bind("Pay", "Wechatpay");
    $app->bind("tryToPayMyBill", "PayBill");
    $paybill = $app->make("tryToPayMyBill"); 
    $paybill->payMyBill();

    是不是很简单呢, 只要把绑定从'Alipay' 改成 'Wechatpay' 就行了,其他的都不用改. 这就是为什么我们要用接口. 只要你的支付方式继承了pay 这个接口,并且实现pay 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.

  • 相关阅读:
    【iOS】获取App的常用文件路径
    【iOS】如何在Objective-C中声明Block?
    实用终端小命令
    iOS开发之NSBundle加载自定义cell需指定其的identifier
    【转】nonatomic, retain,weak,strong用法详解
    iOS控制台打印NSLog增强版
    iOS 内存中的ViewController释放
    iOS ViewController生命周期
    Netty (一) IO 基础篇
    多线程(七) 线程池的实现原理分析
  • 原文地址:https://www.cnblogs.com/BrokenHeart/p/10681401.html
Copyright © 2011-2022 走看看