PHP 依赖注入,从此不再考虑加载顺序
說這個(gè)話題之前先講一個(gè)比較高端的思想--'依賴倒置原則'
"依賴倒置是一種軟件設(shè)計(jì)思想,在傳統(tǒng)軟件中,上層代碼依賴于下層代碼,當(dāng)下層代碼有所改動(dòng)時(shí),上層代碼也要相應(yīng)進(jìn)行改動(dòng),因此維護(hù)成本較高。而依賴倒置原則的思想是,上層不應(yīng)該依賴下層,應(yīng)依賴接口。意為上層代碼定義接口,下層代碼實(shí)現(xiàn)該接口,從而使得下層依賴于上層接口,降低耦合度,提高系統(tǒng)彈性"
?
上面的解釋有點(diǎn)虛,下面我們以實(shí)際代碼來解釋這個(gè)理論
比如有這么條需求,用戶注冊(cè)完成后要發(fā)送一封郵件,然后你有如下代碼:
先有郵件類'Email.class.php'
class Mail{public function send(){/*這里是如何發(fā)送郵件的代碼*/} }然后又注冊(cè)的類'Register.class.php'
class Register{private $_emailObj;public function doRegister(){/*這里是如何注冊(cè)*/$this->_emailObj = new Mail();$this->_emailObj->send();//發(fā)送郵件 } }然后開始注冊(cè)
include 'Mail.class.php'; include 'Register.class.php'; $reg = new Register(); $reg->doRegister();看起來事情很簡(jiǎn)單,你很快把這個(gè)功能上線了,看起來相安無事... xxx天過后,產(chǎn)品人員說發(fā)送郵件的不好,要使用發(fā)送短信的,然后你說這簡(jiǎn)單我把'Mail'類改下...
又過了幾天,產(chǎn)品人員說發(fā)送短信費(fèi)用太高,還是改用郵件的好... ?此時(shí)心中一萬個(gè)草泥馬奔騰而過...
這種事情,常常在產(chǎn)品狗身上發(fā)生,無可奈何花落去...
?
以上場(chǎng)景的問題在于,你每次不得不對(duì)'Mail'類進(jìn)行修改,代碼復(fù)用性很低,高層過度依賴于底層。那么我們就考慮'依賴倒置原則',讓底層繼承高層制定的接口,高層依賴于接口。
interface Mail {public function send(); } class Email implements Mail() {public function send(){//發(fā)送Email } } class SmsMail implements Mail() {public function send(){//發(fā)送短信 } } class Register {private $_mailObj;public function __construct(Mail $mailObj){$this->_mailObj = $mailObj;}public function doRegister(){/*這里是如何注冊(cè)*/$this->_mailObj->send();//發(fā)送信息 } }?下面開始發(fā)送信息
/* 此處省略若干行 */ $reg = new Register(); $emailObj = new Email(); $smsObj = new SmsMail();$reg->doRegister($emailObj);//使用email發(fā)送 $reg->doRegister($smsObj);//使用短信發(fā)送 /* 你甚至可以發(fā)完郵件再發(fā)短信 */上面的代碼解決了'Register'對(duì)信息發(fā)送類的依賴,使用構(gòu)造函數(shù)注入的方法,使得它只依賴于發(fā)送短信的接口,只要實(shí)現(xiàn)其接口中的'send'方法,不管你怎么發(fā)送都可以。上例就使用了"注入"這個(gè)思想,就像注射器一樣將一個(gè)類的實(shí)例注入到另一個(gè)類的實(shí)例中去,需要用什么就注入什么。當(dāng)然"依賴倒置原則"也始終貫徹在里面。"注入"不僅可以通過構(gòu)造函數(shù)注入,也可以通過屬性注入,上面你可以可以通過一個(gè)"setter"來動(dòng)態(tài)為"mailObj"這個(gè)屬性賦值。
?
上面看了很多,但是有心的讀者可能會(huì)發(fā)現(xiàn)標(biāo)題中"從此不再考慮加載順序"這個(gè)字眼,你上面的不還是要考慮加載順序嗎? 不還是先得引入信息發(fā)送類,然后在引入注冊(cè)類,然后再實(shí)例化嗎? 如果類一多,不照樣暈!
確實(shí)如此,現(xiàn)實(shí)中有許多這樣的案例,一開始類就那么多,慢慢的功能越來越多,人員越來越多,編寫了很多類,要使用這個(gè)類必須先引入那個(gè)類,而且一定要確保順序正確。有這么個(gè)例子, "a 依賴于b, b 依賴于c, c 依賴于 d, d 依賴于e", 要獲取'a'的實(shí)例,你必須依次引入 'e,d,c,b'然后依次進(jìn)行實(shí)例化,老的員工知道這個(gè)坑,跳過去了。某天來了個(gè)新人,他想實(shí)例化'a' 可是一直報(bào)錯(cuò),他都不造咋回事,此時(shí)只能看看看'a'的業(yè)務(wù)邏輯,然后知道要先獲取'b'的實(shí)例,然后在看'b'的業(yè)務(wù)邏輯,然后... 一天過去了,他還是沒有獲取到'a'的實(shí)例,然后領(lǐng)導(dǎo)來了...
?
那這個(gè)事情到底是新人的技術(shù)低下,還是當(dāng)時(shí)架構(gòu)人員的水平低下了?
?
現(xiàn)在切入話題,來實(shí)現(xiàn)如何不考慮加載順序,在實(shí)現(xiàn)前就要明白要是不考慮加載順序就意味著讓程序自動(dòng)進(jìn)行加載自動(dòng)進(jìn)行實(shí)例化。類要實(shí)例化,只要保證完整的傳遞給'__construct'函數(shù)所必須的參數(shù)就OK了,在類中如果要引用其他類,也必須在構(gòu)造函數(shù)中注入,否則調(diào)用時(shí)仍然會(huì)發(fā)生錯(cuò)誤。那么我們需要一個(gè)類,來保存類實(shí)例化所需要的參數(shù),依賴的其他類或者對(duì)象以及各個(gè)類實(shí)例化后的引用
該類命名為盒子 'Container.class.php', 其內(nèi)容如下:
/** * 依賴注入類 */ class Container{/***@var array 存儲(chǔ)各個(gè)類的定義 以類的名稱為鍵*/private $_definitions = array();/***@var array 存儲(chǔ)各個(gè)類實(shí)例化需要的參數(shù) 以類的名稱為鍵*/private $_params = array();/***@var array 存儲(chǔ)各個(gè)類實(shí)例化的引用*/private $_reflections = array();/*** @var array 各個(gè)類依賴的類*/private $_dependencies = array();/*** 設(shè)置依賴* @param string $class 類、方法 名稱* @param mixed $defination 類、方法的定義* @param array $params 類、方法初始化需要的參數(shù)*/public function set($class, $defination = array(), $params = array()){$this->_params[$class] = $params;$this->_definitions[$class] = $this->initDefinition($class, $defination);}/*** 獲取實(shí)例* @param string $class 類、方法 名稱* @param array $params 實(shí)例化需要的參數(shù)* @param array $properties 為實(shí)例配置的屬性* @return mixed*/public function get($class, $params = array(), $properties = array()){if(!isset($this->_definitions[$class])){//如果重來沒有聲明過 則直接創(chuàng)建return $this->bulid($class, $params, $properties);}$defination = $this->_definitions[$class];if(is_callable($defination, true)){//如果聲明是函數(shù)$params = $this->parseDependencies($this->mergeParams($class, $params));$obj = call_user_func($defination, $this, $params, $properties);}elseif(is_array($defination)){$originalClass = $defination['class'];unset($definition['class']);//difinition中除了'class'元素外 其他的都當(dāng)做實(shí)例的屬性處理$properties = array_merge((array)$definition, $properties);//合并該類、函數(shù)聲明時(shí)的參數(shù)$params = $this->mergeParams($class, $params);if($originalClass === $class){//如果聲明中的class的名稱和關(guān)鍵字的名稱相同 則直接生成對(duì)象$obj = $this->bulid($class, $params, $properties);}else{//如果不同則有可能為別名 則從容器中獲取$obj = $this->get($originalClass, $params, $properties);}}elseif(is_object($defination)){//如果是個(gè)對(duì)象 直接返回return $defination;}else{throw new Exception($class . ' 聲明錯(cuò)誤!');}return $obj;}/*** 合并參數(shù)* @param string $class 類、函數(shù) 名稱* @param array $params 參數(shù)* @return array*/protected function mergeParams($class, $params = array()){if(empty($this->_params[$class])){return $params;}if(empty($params)){return $this->_params;}$result = $this->_params[$class];foreach($params as $key => $value) {$result[$key] = $value;}return $result;}/*** 初始化聲明* @param string $class 類、函數(shù) 名稱* @param array $defination 類、函數(shù)的定義* @return mixed*/protected function initDefinition($class, $defination){if(empty($defination)){return array('class' => $class);}if(is_string($defination)){return array('class' => $defination);}if(is_callable($defination) || is_object($defination)){return $defination;}if(is_array($defination)){if(!isset($defination['class'])){$definition['class'] = $class;}return $defination;}throw new Exception($class. ' 聲明錯(cuò)誤');}/*** 創(chuàng)建類實(shí)例、函數(shù)* @param string $class 類、函數(shù) 名稱* @param array $params 初始化時(shí)的參數(shù)* @param array $properties 屬性* @return mixed*/protected function bulid($class, $params, $properties){list($reflection, $dependencies) = $this->getDependencies($class);foreach ((array)$params as $index => $param) {//依賴不僅有對(duì)象的依賴 還有普通參數(shù)的依賴$dependencies[$index] = $param;}$dependencies = $this->parseDependencies($dependencies, $reflection);$obj = $reflection->newInstanceArgs($dependencies);if(empty($properties)){return $obj;}foreach ((array)$properties as $name => $value) {$obj->$name = $value;}return $obj;}/*** 獲取依賴* @param string $class 類、函數(shù) 名稱* @return array*/protected function getDependencies($class){if(isset($this->_reflections[$class])){//如果已經(jīng)實(shí)例化過 直接從緩存中獲取return array($this->_reflections[$class], $this->_dependencies[$class]);}$dependencies = array();$ref = new ReflectionClass($class);//獲取對(duì)象的實(shí)例$constructor = $ref->getConstructor();//獲取對(duì)象的構(gòu)造方法if($constructor !== null){//如果構(gòu)造方法有參數(shù)foreach($constructor->getParameters() as $param) {//獲取構(gòu)造方法的參數(shù)if($param->isDefaultValueAvailable()){//如果是默認(rèn) 直接取默認(rèn)值$dependencies[] = $param->getDefaultValue();}else{//將構(gòu)造函數(shù)中的參數(shù)實(shí)例化$temp = $param->getClass();$temp = ($temp === null ? null : $temp->getName());$temp = Instance::getInstance($temp);//這里使用Instance 類標(biāo)示需要實(shí)例化 并且存儲(chǔ)類的名字$dependencies[] = $temp;}}}$this->_reflections[$class] = $ref;$this->_dependencies[$class] = $dependencies;return array($ref, $dependencies);}/*** 解析依賴* @param array $dependencies 依賴數(shù)組* @param array $reflection 實(shí)例* @return array $dependencies*/protected function parseDependencies($dependencies, $reflection = null){foreach ((array)$dependencies as $index => $dependency) {if($dependency instanceof Instance){if ($dependency->id !== null) {$dependencies[$index] = $this->get($dependency->id);} elseif($reflection !== null) {$parameters = $reflection->getConstructor()->getParameters();$name = $parameters[$index]->getName();$class = $reflection->getName();throw new Exception('實(shí)例化類 ' . $class . ' 時(shí)缺少必要參數(shù):' . $name);} }}return $dependencies;} }?
下面是'Instance'類的內(nèi)容,該類主要用于記錄類的名稱,標(biāo)示是否需要獲取實(shí)例
class Instance{/*** @var 類唯一標(biāo)示*/public $id;/*** 構(gòu)造函數(shù)* @param string $id 類唯一ID* @return void*/public function __construct($id){$this->id = $id;}/*** 獲取類的實(shí)例* @param string $id 類唯一ID* @return Object Instance*/public static function getInstance($id){return new self($id);} }然后我們?cè)?#39;Container.class.php'中還是實(shí)現(xiàn)了為類的實(shí)例動(dòng)態(tài)添加屬性的功能,若要?jiǎng)討B(tài)添加屬性,需使用魔術(shù)方法'__set'來實(shí)現(xiàn),因此所有使用依賴加載的類需要實(shí)現(xiàn)該方法,那么我們先定義一個(gè)基礎(chǔ)類 'Base.class.php',內(nèi)容如下
class Base{/*** 魔術(shù)方法* @param string $name* @param string $value* @return void*/public function __set($name, $value){$this->{$name} = $value;} }然后我們來實(shí)現(xiàn)'A,B,C'類,A類的實(shí)例 依賴于 B類的實(shí)例,B類的實(shí)例依賴于C類的實(shí)例
'A.class.php'
class A extends Base{private $instanceB;public function __construct(B $instanceB){$this->instanceB = $instanceB;}public function test(){$this->instanceB->test();} }'B.class.php'
class B extends Base{private $instanceC;public function __construct(C $instanceC){$this->instanceC = $instanceC;}public function test(){return $this->instanceC->test();} }'C.class.php'
class C extends Base{public function test(){echo 'this is C!';} }de然后我們?cè)?#39;index.php'中獲取'A'的實(shí)例,要實(shí)現(xiàn)自動(dòng)加載,需要使用SPL類庫(kù)的'spl_autoload_register'方法,代碼如下
function autoload($className) {include_once $className . '.class.php'; } spl_autoload_register('autoload', true, true); $container = new Container;$a = $container->get('A'); $a->test();//輸出 'this is C!'上面的例子看起來是不是很爽,根本都不需要考慮'B','C' (當(dāng)然,這里B,C 除了要使用相應(yīng)類的實(shí)例外,沒有其他參數(shù),如果有其他參數(shù),必須顯要調(diào)用'$container->set(xx)'方法進(jìn)行注冊(cè),為其制定實(shí)例化必要的參數(shù))。有細(xì)心同學(xué)可能會(huì)思考,比如我在先獲取了'A'的實(shí)例,我在另外一個(gè)地方也要獲取'A'的實(shí)例,但是這個(gè)地方'A'的實(shí)例需要其中某個(gè)屬性不一樣,我怎么做到?
你可以看到'Container' 類的 'get' 方法有其他兩個(gè)參數(shù),'$params' 和 '$properties' , 這個(gè)'$properties' 即可實(shí)現(xiàn)剛剛的需求,這都依賴'__set'魔術(shù)方法,當(dāng)然這里你不僅可以注冊(cè)類,也可以注冊(cè)方法或者對(duì)象,只是注冊(cè)方法時(shí)要使用回調(diào)函數(shù),例如
$container->set('foo', function($container, $params, $config){print_r($params);print_r($config); });$container->get('foo', array('name' => 'foo'), array('key' => 'test'));還可以注冊(cè)一個(gè)對(duì)象的實(shí)例,例如
class Test {public function mytest(){echo 'this is a test';} }$container->set('testObj', new Test());$test = $container->get('testObj'); $test->mytest();?
?以上自動(dòng)加載,依賴控制的大體思想就是將類所要引用的實(shí)例通過構(gòu)造函數(shù)注入到其內(nèi)部,在獲取類的實(shí)例的時(shí)候通過PHP內(nèi)建的反射解析構(gòu)造函數(shù)的參數(shù)對(duì)所需要的類進(jìn)行加載,然后進(jìn)行實(shí)例化,并進(jìn)行緩存以便在下次獲取時(shí)直接從內(nèi)存取得
?
以上代碼僅僅用于學(xué)習(xí)和實(shí)驗(yàn),未經(jīng)嚴(yán)格測(cè)試,請(qǐng)不要用于生產(chǎn)環(huán)境,以免產(chǎn)生未知bug
?
鄙人才疏學(xué)淺,有不足之處,歡迎補(bǔ)足!
轉(zhuǎn)載于:https://www.cnblogs.com/painsOnline/p/5138806.html
總結(jié)
以上是生活随笔為你收集整理的PHP 依赖注入,从此不再考虑加载顺序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IOS日期的处理
- 下一篇: libSVM在matlab下的使用安装