本文首發於 深刻淺出依賴注入,轉載請註明出處。
本文試圖以一種易於理解的行文講解什麼是「依賴注入」這種設計模式。php
或許您已經在項目中已經使用過「依賴注入」,只不過因爲某些緣由,導致您對它的印象不是特別深入。html
「依賴注入」多是最簡單的設計模式之一,但即使如此我發現要想真正的以一種老小咸宜的方式把它講解透徹也絕非易事。sql
本文在寫做過程當中參考了諸多優秀的與「依賴注入」相關文章,我會從如下幾個方面給你們講解「依賴注入」到底是一種怎樣的設計模式:數據庫
什麼是「組件」和「服務」segmentfault
什麼是控制反轉和依賴注入設計模式
如何實現依賴注入緩存
依賴注入的優缺點cookie
如何選擇依賴注入的方式session
提示:本文內容較多,會耗費較多的閱讀實現,建議抽取空閒時間進行閱讀;建議不要錯過參考資料部分的學習;另外,因爲本人技術水平所限表述不到的地方歡迎指正。異步
若是您以爲本文對您有幫助,在收藏的同時請隨手點個「贊」,謝謝!
在講解什麼是依賴注入以前,咱們須要對什麼是依賴這個問題進行說明。
所謂的「依賴」就是指在實現某個功能模塊時須要使用另一個(或多個)「組件」或「服務」,那麼這個所需的「組件」或「服務」將被稱爲「依賴」。
後續文中統一使用「組件」表示某個模塊的「依賴」,「依賴注入」就是指向使用者注入某個「組件」以供其使用。
「組件」:它是可能被做者沒法控制的其它應用使用,但使用者不能對其源碼進行修改的一個功能模塊。
「服務」指:使用者以同步(或異步)請求遠程接口來遠程使用的一個功能接口。
「組件」和「服務」的 共同之處 就是它們都將被其餘應用程序或功能模塊使用。
它們的不一樣之處在於:
「控制反轉」和「依賴注入」本質上就是一個從 問題發現 到 實現 的過程。
即在項目中咱們經過使用「依賴注入」這種技術手段實現功能模塊對其依賴組件的「控制反轉」。
咱們在開發的過程當中時長會遇到這樣一個問題:如何才能將不一樣的「組件」進行組裝才能讓它們配合默契的完成某個模塊的功能?
「依賴注入」就是爲了完成這樣的 目標:將 依賴組件 的配置和使用分離開,以下降使用者與依賴之間的耦合度。
在闡述「依賴注入」這個模式具體含義前,仍是先看一個常見的示例,或許對於理解更有幫助。
這個示例的靈感來自 What is Dependency Injection? 這篇文章(譯文 什麼是依賴注入?)。
從事服務端研發工做的同窗,應該有這樣的體驗。
因爲 HTTP 協議是一種無狀態的協議,因此咱們就須要使用「Session(會話)」機制對有狀態的信息進行存儲。一個典型的應用場景就是存儲登陸用戶的狀態到會話中。
<?php $user = ['uid' => 1, 'uname' => '柳公子']; $_SESSION['user'] = $user;
上面這段代碼將登陸用戶 $user 存儲「會話」的 user 變量內。以後,同一個用戶發起請求就能夠直接從「會話」中獲取這個登陸用戶數據:
<?php $user = $_SESSION['user'];
接着,咱們將這段面向過程的代碼,以面向對象的方法進行封裝:
<?php class SessionStorage { public function __construct($cookieName = 'PHP_SESS_ID') { session_name($cookieName); session_start(); } public function set($key, $value) { $_SESSION[$key] = $value; } public function get($key) { return $_SESSION[$key]; } public function exists($key) { return isset($this->get($key)); } }
而且須要提供一個接口服務類 user:
<?php class User { protected $storage; public function __construct() { $this->storage = new SessionStorage(); } public function login($user) { if (!$this->storage->exists('user')) { $this->storage->set('user', $user); } return 'success'; } public function getUser() { return $this->storage->get('user'); } }
以上就是登陸所需的大體功能,使用起來也很是容易:
<?php $user = new User(); $user->login(['uid' => 1, 'uname' => '柳公子']); $loginUser = $user->getUser();
這個功能實現很是簡單:用戶登陸 login() 方法依賴於 $this->storage 存儲對象,這個對象完成將登陸用戶的信息存儲到「會話」的處理。
那麼對於這個功能的實現,究竟還有什麼值得咱們去擔憂呢?
一切彷佛幾近完美,直到咱們的業務作大了,會發現經過「會話」機制存儲用戶的登陸信息已近沒法知足需求了,咱們須要使用「共享緩存」來存儲用戶的登陸信息。這個時候就會發現:
User 對象的 login() 方法依賴於 $this->storage 這個具體實現,即耦合到一塊兒了。這個就是咱們須要面對的 核心問題。
既然咱們已經發現了問題的癥結所在,也就很容易獲得 解決方案:讓咱們的 User 對象不依賴於具體的存儲方式,但不管哪一種存儲方式,都須要提供 set 方法執行存儲用戶數據。
具體實現能夠分爲如下幾個階段:
定義 Storage 接口的做用是: 使 User 與 SessionStorage 實現類進行解耦,這樣咱們的 User 類便再也不依賴於具體的實現了。
編寫一個 Storage 接口彷佛不會太複雜:
<?php interface Storage { public function set($key, $value); public function get($key); public function exists($key); }
而後讓 SessionStorage 類實現 Storage 接口:
<?php class SessionStorage implements Storage { public function __construct($cookieName = 'PHP_SESS_ID') { session_name($cookieName); session_start(); } public function set($key, $value) { $_SESSION[$key] = $value; } public function get($key) { return $_SESSION[$key]; } public function exists($key) { return isset($this->get($key)); } }
如今咱們的 User 類看起來既依賴於 Storage 接口又依賴於 SessionStorage 這個具體實現:
<?php class User { protected $storage; public function __construct() { $this->storage = new SessionStorage(); } }
固然這已是一個完美的登陸功能了,直到我將這個功能開放出來給別人使用。然而,若是這個應用一樣是經過「會話」機制來存儲用戶信息,現有的實現不會出現問題。
但若是使用者將「會話」機制更換到下列這些存儲方式呢?
<?php // 想象下下面的全部實現類都有實現 get,set 和 exists 方法 class MysqlStorage {} class MemcachedStorage {} class RedisStorage {} class MongoDBStorage {} ...
此時咱們彷佛沒法在不修改 User 類的構造函數的的狀況下,完成替換 SessionStorage 類的實例化過程。即咱們的模塊與依賴的具體實現類耦合到一塊兒了。
有沒有這樣一種解決方案,讓咱們的模塊僅依賴於接口類,而後在項目運行階段動態的插入具體的實現類,而非在編譯(或編碼)階段將實現類接入到使用場景中呢?
這種動態接入的能力稱爲「插件」。
答案是有的:可使用「控制反轉」。
「控制反轉」提供了將「插件」組合進模塊的能力。
在實現「控制反轉」過程當中咱們「反轉」了哪方面的「控制」呢?其實這裏的「反轉」的意義就是 如何去定位「插件」的具體實現。
採用「控制反轉」模式時,咱們經過一個組裝模塊,將「插件」的具體實現「注入」到模塊中就能夠了。
瞭解完「控制反轉」,咱們再來看看什麼是「依賴注入」。「依賴注入」和「控制反轉」之間是怎樣的一種關係呢?
「控制反轉」是目的:它但願咱們的模塊可以在運行時動態獲取依賴的「插件」,而後,咱們經過「依賴注入」這種手段去完成「控制反轉」的目的。
這邊我試着給出一個「依賴注入」的具體的定義:
應用程序對須要使用的依賴「插件」在編譯(編碼)階段僅依賴於接口的定義,到運行階段由一個獨立的組裝模塊(容器)完成對實現類的實例化工做,並將其「注射」到應用程序中稱之爲「依賴注入」。
如何實現依賴注入或者說依賴注入有哪些形式?
在 Inversion of Control Containers and the Dependency Injection pattern 一文中有過相關的闡述:
依賴注入的形式主要有三種,我分別將它們叫作構造注入( Constructor Injection)、設值
方法注入( Setter Injection)和接口注入( Interface Injection)
本文將結合上面的示例稍微講下:
這兩種注入方式。
經過前面的文章咱們知道 User 類的構造函數既依賴於 Storage 接口,又依賴於 SessionStorage 這個具體的實現。
如今咱們經過重寫 User 類的構造函數,使其僅依賴於 Storage 接口:
<?php class User { protected $storage; public function __construct(Storage $storage) { $this->storage = $storage; } }
咱們知道 User 類中的 login 和 getUser 方法內依賴的是 $this->storage 實例,也就無需修改這部分的代碼了。
以後咱們就能夠經過「依賴注入」完成將 SessionStorage 實例注入到 User 類中,實現高內聚低耦合的目標:
<?php $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
設值注入也很簡單:
<?php class User { protected $storage; public function setStorage(Storage $storage) { $this->storage = $storage; } }
使用也幾乎和構造方法注入同樣:
<?php $storage = new SessionStorage('SESSION_ID'); $user = new User(); $user->setStorage($storage);
上面實現依賴注入的過程僅僅能夠當作一個演示,真實的項目中確定沒有這樣使用的。那麼咱們在項目中該如何去實現依賴注入呢?
嗯,這是個好問題,因此如今咱們須要瞭解另一個與「依賴注入」相關的內容「依賴注入容器」。
依賴注入容器咱們在給「依賴注入」下定義的時候有提到 由一個獨立的組裝模塊(容器)完成對實現類的實例化工做,那麼這個組裝模塊就是「依賴注入容器」。
「依賴注入容器」是一個知道如何去實例化和配置依賴組件的對象。
儘管,咱們已經可以將 User 類與實現分離,可是還須要進一步,才能稱之爲完美。
定義一個簡單的服務容器:
<?php class Container { public function getStorage() { return new SessionStorage(); } public function getUser() { $user = new User($this->getStorage()); return $user; } }
使用也很簡單:
<?php $container = new Container(); $user = $container->getUser();
咱們看到,若是咱們須要使用 User 對象僅須要經過 Container 容器的 getUser 方法便可獲取這個實例,而無需關心它是如何被建立建立出來的。
這樣,咱們就瞭解了「依賴注入」幾乎所有的細節了,可是現實老是會比理想更加骨感。由於,咱們現有的依賴注入容器還至關的脆弱,由於它一樣依賴於 SessionStorage,一旦咱們須要替換這個實現,仍是不得不去修改裏面的源代碼,而沒法實如今運行時配置。
作了這麼多工做,仍是這樣的結果,真是晴天霹靂啊!
爲何不考慮將實現類相關數據寫入到配置文件中,在容器中實例化是從配置文件中讀取呢?
有關使用依賴注入容器的更加詳細的使用能夠閱讀我翻譯的 依賴注入 系列文章,文章還部分篇章沒有翻譯,因此你也能夠直接閱讀 原文。
前兩個比較好理解,稍微說下依賴注入是如何簡化測試的。
若是咱們在實現 User 類時,尚未實現具體的 SessionStorage 類,而僅定義了 Storage 接口。
那麼在測試時,能夠編寫一個 NopStorage 先用於測試,以後等實現了 SessionStorage 在進行替換便可。
組件與注入器之間不會有依賴關係,所以組件沒法從注入器那裏得到更多的服務,只能得到配置信息中所提供的那些。
如何選擇依賴注入方式在 Inversion of Control Containers and the Dependency Injection pattern 一文中有給出相關論述。
說完了什麼是「控制反轉」和「依賴注入」,相信你們已經對這兩個概念有了相對比較清晰的瞭解。我想說的是任何事物的瞭解程度都不是一蹴而就的,因此即使有號稱能一句話講明白什麼是「依賴注入」的文章,其實仍是須要咱們有了相對深刻的瞭解後才能感悟其中的真意,所謂「讀書百遍,其義自見」就是這個道理。