博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何理解laravel 的 IOC 容器
阅读量:5742 次
发布时间:2019-06-18

本文共 11117 字,大约阅读时间需要 37 分钟。

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 $payMethod;33 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 //constructor37 ReflectionMethod {
#351 38 +name: "__construct" 39 +class: "A" 40 parameters: array:1 [] 41 extra: array:3 [] 42 modifiers: "public"43 }44 45 //$dependencies46 array:1 [47 0 => ReflectionParameter {
#352 48 +name: "args"49 position: 050 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 
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 Alipay3 $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  "App\Http\Controllers\Pay" => array:2 [ 3      "concrete" => Closure {
#355 4 class: "App\Http\Controllers\Container" 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" => "App\Http\Controllers\Pay"11 "$concrete" => "App\Http\Controllers\Alipay"12 ] 13 file: "C:\project\test\app\Http\Controllers\IOCController.php" line: "119 to 122"14 } 15 "shared" => false 16 ]17 18 "tryToPayMyBill" => array:2 [19 "concrete" => Closure {
#359 20 class: "App\Http\Controllers\Container" 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" => "\App\Http\Controllers\PayBill"28 ] 29 file: "C:\project\test\app\Http\Controllers\IOCController.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: "App\Http\Controllers\PayBill" 5 parameters: array:1 [ 6 "$payMethod" => ReflectionParameter {
#371 7 +name: "payMethod" 8 position: 0 typeHint: "App\Http\Controllers\Pay" 9 }10 ]11 extra: array:3 [12 "file" => "C:\project\test\app\Http\Controllers\IOCController.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: "App\Http\Controllers\Pay"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 这个方法,我们就能够通过绑定正常的使用. 这样我们的程序就非常容易被拓展,因为以后可能会出现成百上千种的支付方式.

转载于:https://www.cnblogs.com/BrokenHeart/p/10681401.html

你可能感兴趣的文章
容器存储中那些潜在的挑战和机遇
查看>>
R语言的三种聚类方法
查看>>
55%受访企业将物联网战略视为有效竞争手段
查看>>
深入理解Python中的ThreadLocal变量(上)
查看>>
如果一切即服务,为什么需要数据中心?
查看>>
《游戏开发物理学(第2版)》一导读
查看>>
Erlang简史(翻译)
查看>>
深入实践Spring Boot2.4.2 节点和关系实体建模
查看>>
信息可视化的经典案例:伦敦地铁线路图
查看>>
10个巨大的科学难题需要大数据解决方案
查看>>
Setting Up a Kerberos server (with Debian/Ubuntu)
查看>>
用 ThreadLocal 管理用户session
查看>>
setprecision后是要四舍五入吗?
查看>>
shiro初步 shiro授权
查看>>
上云就是这么简单——阿里云10分钟快速入门
查看>>
MFC多线程的创建,包括工作线程和用户界面线程
查看>>
我的友情链接
查看>>
FreeNAS8 ISCSI target & initiator for linux/windows
查看>>
cvs文件提交冲突解决方案
查看>>
PostgreSQL数据库集群初始化
查看>>