【modernPHP專題(3)】依賴注入與服務容器
依賴倒置和控制反轉是一種程式設計思想,而依賴注入就是通過服務容器實現這種面向介面或者是面向抽象程式設計的思想
概念理解
依賴倒置原則
依賴倒置是一種軟體設計思想,在傳統軟體中,上層程式碼依賴於下層程式碼,當下層程式碼有所改動時,上層程式碼也要相應進行改動,因此維護成本較高。而依賴倒置原則的思想是,上層不應該依賴下層,應依賴介面 。意為上層程式碼定義介面,下層程式碼實現該介面,從而使得下層依賴於上層介面,降低耦合度,提高系統彈性
控制反轉
當呼叫者需要被呼叫者的協助時,在傳統的程式設計過程中,通常由呼叫者來建立被呼叫者的例項,但在這裡,建立被呼叫者例項的工作不再由呼叫者來完成,而是將被呼叫者的建立移到呼叫者的外部,從而反轉被呼叫者的建立,消除了呼叫者對被呼叫者建立的控制,因此稱為控制反轉。
要實現控制反轉,通常的解決方案是將建立被呼叫者例項的工作交由 IoC 容器來完成,然後在呼叫者中注入被呼叫者(通過構造器/方法注入實現),這樣我們就實現了呼叫者與被呼叫者的解耦,該過程被稱為依賴注入。
依賴注入不是目的,它是一系列工具和手段,最終的目的是幫助我們開發出鬆散耦合(loose coupled)、可維護、可測試的程式碼和程式。這條原則的做法是大家熟知的面向介面,或者說是面向抽象程式設計 。
通俗的說,在呼叫一個物件的方法,首先要例項化物件之後。 而所謂的注入,就是一種工廠模式的昇華。由一個更高階的工廠(容器),來完成物件例項化,實現呼叫者與被呼叫者的解耦
解決什麼問題
實現呼叫者與被呼叫者的解耦
[info] 所謂的上層程式碼依賴於介面 ,就是業務邏輯的實現是跳過了具體物件的抽象行為。比如我們要對使用者發訊息,可以通過郵件傳送,也可以通過簡訊傳送。上層程式碼不用關注其用什麼傳送,只發送即可(介面卡模式)
interface Mail { public function send(); } class Email implements Mail { public function send() { echo '傳送郵件' . PHP_EOL; } } class SmsMail implements Mail { public function send() { echo '傳送簡訊' . PHP_EOL; } } // 註冊容器 class Register { private $_mailObj; // 建構函式裡面已經約束了必須是實現了Mail介面的類的例項 public function __construct(Mail $mailObj) { $this->_mailObj = $mailObj; } public function doRegister() { // 一定會有send方法 $this->_mailObj->send();//傳送資訊 } } $emailObj = new Email(); $smsObj = new SmsMail(); $reg = new Register($emailObj); $reg->doRegister();//使用email傳送 $reg = new Register($smsObj); $reg->doRegister($smsObj);//使用簡訊傳送
使用建構函式注入的方法,使得它只依賴於傳送簡訊的介面,只要實現其介面中的'send'方法,不管你什麼方式傳送都可以。上面通過建構函式注入物件的方式,就是最簡單的依賴注入;當然"注入"不僅可以通過建構函式注入,也可以通過屬性注入,上面你可以通過一個"setter"來動態為"mailObj"這個屬性賦值。
通過php反射機制實現自動注入
真實的dependency injection container會提供更多的特性,如
- 自動繫結(Autowiring)或 自動解析(Automatic Resolution)
- 註釋解析器(Annotations)
- 延遲注入(Lazy injection)
<?php class C { public function doSomething() { echo __METHOD__ , '我是周伯通C|'; } } class B { private $c; public function __construct(C $c) { $this->c = $c; } public function doSomething() { $this->c->doSomething(); echo __METHOD__ , '我是周伯通B|'; } } class A { private $b; public function __construct(B $b) { $this->b = $b; } public function doSomething() { $this->b->doSomething(); echo __METHOD__ , '我是周伯通A|';; } } class Container { private $s = []; public function __set($k , $c) { $this->s[$k] = $c; } public function __get($k) { return $this->build($this->s[$k]); } /** * 自動繫結(Autowiring)自動解析(Automatic Resolution) * @param string $className * @return object * @throws Exception */ public function build($className) { // 如果是匿名函式(Anonymous functions),也叫閉包函式(closures) if ($className instanceof Closure) { // 執行閉包函式,並將結果 return $className($this); } if(!class_exists($className)){ throw new Exception("{$className} class is not exists"); } /** @var ReflectionClass $reflector */ $reflector = new ReflectionClass($className); // 檢查類是否可例項化, 排除抽象類abstract和物件介面interface if (!$reflector->isInstantiable()) { throw new Exception("Can't instantiate this."); } /** @var ReflectionMethod $constructor 獲取類的建構函式 */ $constructor = $reflector->getConstructor(); // 若無建構函式,直接例項化並返回, (注意! 此處退出遞迴1) if (is_null($constructor)) { return new $className; } // 取建構函式引數,通過 ReflectionParameter 陣列返回引數列表 $parameters = $constructor->getParameters(); // 遞迴解析建構函式的引數 $dependencies = $this->getDependencies($parameters); // 建立一個類的新例項,給出的引數將傳遞到類的建構函式。 return $reflector->newInstanceArgs($dependencies); } /** * @param array $parameters * @return array * @throws Exception */ public function getDependencies($parameters) { $dependencies = []; /** @var ReflectionParameter $parameter */ foreach ($parameters as $parameter) { /** @var ReflectionClass $dependency */ $dependency = $parameter->getClass(); if (is_null($dependency)) { // 是變數,有預設值則設定預設值 (注意,此處退出遞迴2) $dependencies[] = $this->resolveNonClass($parameter); } else { // 是一個類,遞迴解析 $dependencies[] = $this->build($dependency->name); } } return $dependencies; } /** * @param ReflectionParameter $parameter * @return mixed * @throws Exception */ public function resolveNonClass($parameter) { // 有預設值則返回預設值 if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } throw new Exception('I have no idea what to do here.'); } } /*// example1 $container = new Container(); $container->b = 'B'; $container->a = function ($container){ return new A($container->b); }; // 從容器中取得A $model = $container->a; // output: C::doSomething我是周伯通C|B::doSomething我是周伯通B|A::doSomething我是周伯通A| // 實現依賴自動注入 $model->doSomething();*/ // example2 $di = new Container(); $di->php7 = 'A'; // 自動注入classA /** @var A $php7 */ $foo = $di->php7; $foo->doSomething(); //C::doSomething我是周伯通C|B::doSomething我是周伯通B|A::doSomething我是周伯通A|
參考: