在開始以前要明確一個概念,不論是設計模式,仍是依賴注入等等,都是爲了實現模塊化.所謂模塊化就是但願一個軟件是由不少子模塊組成的,這些模塊之間的依賴程度儘可能的低,也就是若是系統中不須要某一個功能,那麼只要移除這個功能所對應的模塊就能夠了.php
那麼,咱們今天要說的服務容器就是爲了實現上面的功能.你應該聽過,Laravel中的服務容器其本質上是一個IoC容器,可是好像隊IoC又不是很瞭解,講來說去優勢不少,功能很強勁.可是不懂原理怎麼用都不踏實啊.因此,這裏咱們本身來實現一個IoC容器,洞察其本質.編程
在開始以前,先說明一點,閱讀本篇文章至少要保證有一下的基礎知識:設計模式
php反射用法數組
閉包的use用法閉包
若是不懂上面的內容,請先補充.避免閱讀代碼時候產生的不適感.app
<?php /** * Created by PhpStorm. * User: jiayao * Date: 2016/9/1 * Time: 21:41 */ class Container { public $binding = []; /** * @param $abstract * @param null $concrete * @param bool $shared */ public function bind($abstract, $concrete = null, $shared = false) { if (!$concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->binding[$abstract] = compact('concrete', 'shared'); } protected function getClosure($abstract, $concrete) { return function ($c) use ($abstract, $concrete) { $method = ($abstract == $concrete) ? 'build' : 'make'; return $c->$method($concrete); }; } /** * @param $abstract * @return object * */ public function make($abstract) { $concrete = $this->getConcrete($abstract); if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete); } else { $object = $this->make($concrete); } return $object; } /** * @param $concrete * @param $abstract * @return bool */ public function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; } /** * @param $abstract * @return mixed */ protected function getConcrete($abstract) { if (!isset($this->binding[$abstract])) { return $abstract; } return $this->binding[$abstract]['concrete']; } /** * @param $concrete * @return object */ public function build($concrete) { if($concrete instanceof Closure) { return $concrete($this); } //反射... $reflector = new ReflectionClass($concrete); if(!$reflector->isInstantiable()) { echo $message = "Target [$concrete] is not instantiable"; } //獲取要實例化對象的構造函數 $constructor = $reflector->getConstructor(); //沒有定義構造函數,只有默認的構造函數,說明構造函數參數個數爲空 if(is_null($constructor)) { return new $concrete; } //獲取構造函數所須要的全部參數 $dependencies = $constructor->getParameters(); $instances = $this->getDependencies($dependencies); //從給出的數組參數在中實例化對象 return $reflector->newInstanceArgs($instances); } /** * @param $paramters * @return array * 獲取構建類所須要的全部依賴,級構造函數所須要的參數 , */ protected function getDependencies($paramters) { $dependencies = []; foreach ($paramters as $paramter) { //獲取到參數名稱. $dep = $paramter->getClass(); if(is_null($dep)){ $dependencies = null; }else{ $dependencies[] = $this->resolveClass($paramter); } } return (array)$dependencies; } /** * @param ReflectionParameter $parameter * @return object * 實例化 構造函數中所須要的參數. */ protected function resolveClass(ReflectionParameter $parameter) { $name = $parameter->getClass()->name; return $this->make($name); } }
這就是一個IoC容器的實現代碼.乍一看,很麻煩.其實真的蠻麻煩的 =_=,若是是第一次接觸的話,並非那麼好消化,這裏再給出使用IoC容器的代碼框架
<?php /** * Created by PhpStorm. * User: jiayao * Date: 2016/9/1 * Time: 21:37 */ require __DIR__ . '/Container.php'; interface TrafficTool { public function go(); } class Train implements TrafficTool { public function go() { echo "train...."; } } class Leg implements TrafficTool { public function go() { echo "leg.."; } } class Traveller { /** * @var Leg|null|Train * 旅行工具 */ protected $_trafficTool; public function __construct(TrafficTool$trafficTool) { $this->_trafficTool = $trafficTool; } public function visitTibet() { $this->_trafficTool->go(); } } //實例化IoC容器 $app = new Container(); //綁定某一功能到IoC $app->bind('TrafficTool', 'Train'); $app->bind('travellerA', 'Traveller'); // 實例化對象 $tra = $app->make('travellerA'); $tra->visitTibet();
運行例子發現會輸出:train..
.這個例子假設旅行者去青藏旅行,能夠坐火車(train)或者走路(leg)去青藏.模塊化
好了,其實這樣子本篇文章就能夠結束了,由於全部的答案都在IoC容器的實現中, 可是爲了能夠更好的理解上面的代碼,咱們繼續往下分析.函數
首先,但願你能夠運行一下上面的代碼,雖然簡單的運行代碼並不會幫助你理解代碼,可是一個能夠運行的例子會讓人比較踏實,可以更有把握的理解代碼.工具
在深刻每一行代碼以前,咱們從總體上來分析,IoC解決了一個什麼問題?簡單點說,就是咱們再實例化對象的時候不用使用new了,有了IoC容器以後,咱們調用make函數就能夠實例化出一個對象了.然而,你發現,Traveller的構造函數是須要一個參數的,但是咱們好像並無提供這個參數?
這就是IoC強大之處了, 調用make實例化對象的時候,容器會使用反射功能,去分析咱們要實例化對象的構造函數,獲取構造函數所需的每一個參數,而後分別去實例化這些參數,若是實例化這些參數也要參數,那麼就再去實例化參數的參數.....=_=.到最後成功實例化咱們所須要的traveller了.在Container的build函數就是使用反射來實例化對象.
可是,有一個問題了,IoC容器怎麼知道實例化Traveller的時候須要的參數train,而不是leg?
其實,IoC容器什麼都不知道,IoC會實例化哪些對象都是經過bind
函數告訴IoC的,上面的例子兩次調用bind函數,就是告訴Ioc能夠實例化的對象有Train
和Traveller
. 再通俗講就是:當須要當咱們須要TrafficTool
這個服務的時候去實例化Train
這個類,須要一個travellerA
的旅行者的時候去實例化Traveller
類.而Train
這個就是travellerA
就是去青藏的方式. 這樣子若是想要走路去青藏的話只要把$app->bind('Visit', 'Train');
改成$app->bind('Visit', 'Leg');
就能夠.
但是,這上面的這些有什麼意義?直接$tra = new Traveller($trafficTool)
來實例化對象好像也沒有什麼很差的.
使用new來實例化對象的時候,會產生依賴.好比上面
$tra = new Traveller($trafficTool)
,這說明咱們要建立一個Traveller以前得有一個$trafficTool
,即Traveller
依賴於trafficTool
.當使用new來實例化Traveller
的時候,Traveller
和trafficTool
之間就產生了耦合.這樣,這兩個組件就沒辦法分開了.
而使用IoC是怎麼解決這個問題的,以前說過,若是想要若是想要走路去青藏的話只要把$app->bind('Visit', 'Train');
改成$app->bind('Visit', 'Leg');
就能夠.這樣子,使用何種方式去青藏,咱們能夠自由的選擇.
咱們站在Laravel框架設計者的角度去想,設計者確定但願一個框架提供的功能越多越好,可是又要保證強大的同時又不會限制使用者.最好能夠保證使用者想實現什麼奇怪的需求均可以.那麼功能強大可是又不侷限的最好方法就是什麼都不作,提供一個強大的IoC容器.全部須要實現的功能都變成一個個服務,須要什麼服務就把服務註冊(即調用bind函數)到IoC中,而後讓IoC去管理依賴.
開發者想到一個變態的需求:走路去青藏,那麼只要你實現了走路去青藏這個功能,而後把這個功能當作一個服務註冊到IoC中,之後你須要這個服務的時候IoC就幫你實例化這個服務.當開發者迴歸正常以後以爲仍是坐火車去吧,因而不註冊走路這個功能,實現坐火車的功能,而後註冊這個功能.下次IoC實例化的時候就是實例化坐火車這個功能了.
好了,剩下的部分就是一行一行的閱讀Container的代碼了,Laravel框架中的服務容器代碼也是這個樣子,只是功能更增強悍.可是核心是同樣的,上面的代碼懂了之後再使用Laravel框架就會更加遊刃有餘了.
文章雖短.可是內容不少.尤爲是代碼,雖然可能只是短短的一個例子,可是包含了不少內容.值得好好分析,這裏放個彩蛋:Traveller中構造函數參數相似爲TrafficTool,是一個接口.可是實例化的是Train.這裏體現了設計模式的一個原則
面對接口編程,而不是面對實現編程.