摘要:依賴注入控制反轉的一種具體實現方法。接下來,我們使用依賴注入實現控制反轉,使依賴關系倒置依賴被動傳入。從單元測試的角度看,依賴注入更方便和操作,方便了測試人員寫出質量更高的測試代碼。
前言
好的設計會提高程序的可復用性和可維護性,也間接的提高了開發人員的生產力。今天,我們就來說一下在很多框架中都使用的依賴注入。
一些概念要搞清楚什么是依賴注入如何依賴注入,首先我們要明確一些概念。
DIP (Dependence Inversion Principle) 依賴倒置原則:程序要依賴于抽象接口,不要依賴于具體實現。
IOC (Inversion of Control) 控制反轉:遵循依賴倒置原則的一種代碼設計方案,依賴的創建 (控制) 由主動變為被動 (反轉)。
DI (Dependency Injection) 依賴注入:控制反轉的一種具體實現方法。通過參數的方式從外部傳入依賴,將依賴的創建由主動變為被動 (實現了控制反轉)。
光說理論有點不好理解,我們用代碼舉個例子。
首先,我們看依賴沒有倒置時的一段代碼:
class Controller { protected $service; public function __construct() { // 主動創建依賴 $this->service = new Service(12, 13); } } class Service { protected $model; protected $count; public function __construct($param1, $param2) { $this->count = $param1 + $param2; // 主動創建依賴 $this->model = new Model("test_table"); } } class Model { protected $table; public function __construct($table) { $this->table = $table; } } $controller = new Controller;
上述代碼的依賴關系是 Controller 依賴 Service,Service 依賴 Model。從控制的角度來看,Controller 主動創建依賴 Service,Service 主動創建依賴 Model。依賴是由需求方內部產生的,需求方需要關心依賴的具體實現。這樣的設計使代碼耦合性變高,每次底層發生改變(如參數變動),頂層就必須修改代碼。
接下來,我們使用依賴注入實現控制反轉,使依賴關系倒置:
class Controller { protected $service; // 依賴被動傳入。申明要 Service 類的實例 (抽象接口) public function __construct(Service $service) { $this->service = $service; } } class Service { protected $model; protected $count; // 依賴被動傳入 public function __construct(Model $model, $param1, $param2) { $this->count = $param1 + $param2; $this->model = $model; } } class Model { protected $table; public function __construct($table) { $this->table = $table; } } $model = new Model("test_table"); $service = new Service($model, 12, 13); $controller = new Controller($service);
將依賴通過參數的方式從外部傳入(即依賴注入),控制的角度上依賴的產生從主動創建變為被動注入,依賴關系變為了依賴于抽象接口而不依賴于具體實現。此時的代碼得到了解耦,提高了可維護性。
從單元測試的角度看,依賴注入更方便 stub 和 mock 操作,方便了測試人員寫出質量更高的測試代碼。
如何依賴注入,自動注入依賴有了上面的一些理論基礎,我們大致了解了依賴注入是什么,能干什么。
不過雖然上面的代碼可以進行依賴注入了,但是依賴還是需要手動創建。我們可不可以創建一個工廠類,用來幫我們進行自動依賴注入呢?OK,我們需要一個 IOC 容器。
實現一個簡單的 IOC 容器依賴注入是以構造函數參數的形式傳入的,想要自動注入:
我們需要知道需求方需要哪些依賴,使用反射來獲得
只有類的實例會被注入,其它參數不受影響
如何自動進行注入呢?當然是 PHP 自帶的反射功能!
注:關于反射是否影響性能,答案是肯定的。但是相比數據庫連接、網絡請求的時延,反射帶來的性能問題在絕大多數情況下并不會成為應用的性能瓶頸。1.雛形
首先,創建 Container 類,getInstance 方法:
class Container { public static function getInstance($class_name, $params = []) { // 獲取反射實例 $reflector = new ReflectionClass($class_name); // 獲取反射實例的構造方法 $constructor = $reflector->getConstructor(); // 獲取反射實例構造方法的形參 $di_params = []; if ($constructor) { foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果參數是一個類,創建實例 $di_params[] = new $class->name; } } } $di_params = array_merge($di_params, $params); // 創建實例 return $reflector->newInstanceArgs($di_params); } }
這里我們獲取構造方法參數時用到了 ReflectionClass 類,大家可以到官方文檔了解一下該類包含的方法和用法,這里就不再贅述。
ok,有了 getInstance 方法,我們可以試一下自動注入依賴了:
class A { public $count = 100; } class B { protected $count = 1; public function __construct(A $a, $count) { $this->count = $a->count + $count; } public function getCount() { return $this->count; } } $b = Container::getInstance(B::class, [10]); var_dump($b->getCount()); // result is 1102.進階
雖然上面的代碼可以進行自動依賴注入了,但是問題是只能構注入一層。如果 A 類也有依賴怎么辦呢?
ok,我們需要修改一下代碼:
class Container { public static function getInstance($class_name, $params = []) { // 獲取反射實例 $reflector = new ReflectionClass($class_name); // 獲取反射實例的構造方法 $constructor = $reflector->getConstructor(); // 獲取反射實例構造方法的形參 $di_params = []; if ($constructor) { foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果參數是一個類,創建實例,并對實例進行依賴注入 $di_params[] = self::getInstance($class->name); } } } $di_params = array_merge($di_params, $params); // 創建實例 return $reflector->newInstanceArgs($di_params); } }
測試一下:
class C { public $count = 20; } class A { public $count = 100; public function __construct(C $c) { $this->count += $c->count; } } class B { protected $count = 1; public function __construct(A $a, $count) { $this->count = $a->count + $count; } public function getCount() { return $this->count; } } $b = Container::getInstance(B::class, [10]); var_dump($b->getCount()); // result is 130
上述代碼使用遞歸完成了多層依賴的注入關系,程序中依賴關系層級一般不會特別深,遞歸不會造成內存遺漏問題。3.單例
有些類會貫穿在程序生命周期中被頻繁使用,為了在依賴注入中避免不停的產生新的實例,我們需要 IOC 容器支持單例模式,已經是單例的依賴可以直接獲取,節省資源。
為 Container 增加單例相關方法:
class Container { protected static $_singleton = []; // 添加一個實例到單例 public static function singleton($instance) { if ( ! is_object($instance)) { throw new InvalidArgumentException("Object need!"); } $class_name = get_class($instance); // singleton not exist, create if ( ! array_key_exists($class_name, self::$_singleton)) { self::$_singleton[$class_name] = $instance; } } // 獲取一個單例實例 public static function getSingleton($class_name) { return array_key_exists($class_name, self::$_singleton) ? self::$_singleton[$class_name] : NULL; } // 銷毀一個單例實例 public static function unsetSingleton($class_name) { self::$_singleton[$class_name] = NULL; } }
改造 getInstance 方法:
public static function getInstance($class_name, $params = []) { // 獲取反射實例 $reflector = new ReflectionClass($class_name); // 獲取反射實例的構造方法 $constructor = $reflector->getConstructor(); // 獲取反射實例構造方法的形參 $di_params = []; if ($constructor) { foreach ($constructor->getParameters() as $param) { $class = $param->getClass(); if ($class) { // 如果依賴是單例,則直接獲取 $singleton = self::getSingleton($class->name); $di_params[] = $singleton ? $singleton : self::getInstance($class->name); } } } $di_params = array_merge($di_params, $params); // 創建實例 return $reflector->newInstanceArgs($di_params); }4.以依賴注入的方式運行方法
類之間的依賴注入解決了,我們還需要一個以依賴注入的方式運行方法的功能,可以注入任意方法的依賴。這個功能在實現路由分發到控制器方法時很有用。
增加 run 方法
public static function run($class_name, $method, $params = [], $construct_params = []) { if ( ! class_exists($class_name)) { throw new BadMethodCallException("Class $class_name is not found!"); } if ( ! method_exists($class_name, $method)) { throw new BadMethodCallException("undefined method $method in $class_name !"); } // 獲取實例 $instance = self::getInstance($class_name, $construct_params); // 獲取反射實例 $reflector = new ReflectionClass($class_name); // 獲取方法 $reflectorMethod = $reflector->getMethod($method); // 查找方法的參數 $di_params = []; foreach ($reflectorMethod->getParameters() as $param) { $class = $param->getClass(); if ($class) { $singleton = self::getSingleton($class->name); $di_params[] = $singleton ? $singleton : self::getInstance($class->name); } } // 運行方法 return call_user_func_array([$instance, $method], array_merge($di_params, $params)); }
測試:
class A { public $count = 10; } class B { public function getCount(A $a, $count) { return $a->count + $count; } } $result = Container::run(B::class, "getCount", [10]); var_dump($result); // result is 20
ok,一個簡單好用的 IOC 容器完成了,動手試試吧!
完整代碼IOC Container 的完整代碼請見 wazsmwazsm/IOCContainer, 原先是在我的框架 wazsmwazsm/WorkerA 中使用,現在已經作為多帶帶的項目,有完善的單元測試,可以使用到生產環境。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/31304.html
摘要:依賴注入依賴注入一詞是由提出的術語,它是將組件注入到應用程序中的一種行為。就像說的依賴注入是敏捷架構中關鍵元素。類依賴于,所以我們的代碼可能是這樣的創建一個這是一種經典的方法,讓我們從使用構造函數注入開始。 showImg(https://segmentfault.com/img/remote/1460000018806800); 文章轉自:https://learnku.com/la...
摘要:如感興趣,可移步手寫之基于動態創建對象手寫之基于注解動態創建對象今天將詳細介紹如何手寫依賴注入,在運行過程中如何動態地為對象的屬性賦值。完成后在中會有相關的包出現進行注入前需要創建工廠,在運行時從工廠中取出對象為屬性賦值。 前兩篇文章介紹了關于手寫Spring IOC控制反轉,由Spring工廠在運行過程中動態地創建對象的兩種方式。如感興趣,可移步: 手寫Spring之IOC基于xml...
摘要:前言最近在使用框架,看了下他的源碼,發現有很多地方也用到了依賴注入控制反轉,覺得有必要和大家簡單聊一聊什么是依賴注入以及怎么使用它。概念依賴注入和控制反轉是對同一件事情的不同描述,從某個方面講,就是它們描述的角度不同。 前言 最近在使用ThinkPHP5框架,看了下他的源碼,發現有很多地方也用到了依賴注入(控制反轉),覺得有必要和大家簡單聊一聊什么是依賴注入以及怎么使用它。 簡介 I...
摘要:標量參數關聯傳值依賴是自動解析注入的,剩余的標量參數則可以通過關聯傳值,這樣比較靈活,沒必要把默認值的參數放在函數參數最尾部。 更新:github(給個小星星呀) -- 2018-4-11:優化服務綁定方法 ::bind 的類型檢查模式 借助 PHP 反射機制實現的一套 依賴自動解析注入 的 IOC/DI 容器,可以作為 Web MVC 框架 的應用容器 1、依賴的自動注入:你只需要...
摘要:使用的好處知乎的回答不用自己組裝,拿來就用。統一配置,便于修改。 前言 只有光頭才能變強 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡單啦 單例模式你會幾種寫法? 工廠模式理解了沒有? 在刷Spring書籍的時候花了點時間去學習了單例模式和工廠模式,總的來說還是非常值得的! 本來想的是刷完《Spring 實戰 (第4版)》和《精通Spring4.x 企業應用開發實戰》...
閱讀 2491·2021-11-24 09:39
閱讀 3415·2021-11-15 11:37
閱讀 2267·2021-10-08 10:04
閱讀 3977·2021-09-09 11:54
閱讀 1890·2021-08-18 10:24
閱讀 1060·2019-08-30 11:02
閱讀 1805·2019-08-29 18:45
閱讀 1661·2019-08-29 16:33