依賴注入與控制反轉
依賴注入 當我第一次接觸這個詞的時候,我是有些丈二和尚摸不著頭腦的,至今我也是感到比較困惑的,所以今天我們來探索一下Laravel中的依賴注入(dependency injection), 來好好的理解它。 控制反轉 第一印象是好深奧的名詞。。。看上去好像是說反向控制?不懂?那就理順之!
起點
什麼是依賴
沒有你我就活不下去,那麼,你就是我的依賴。 說白了就是:
不是我自身的,卻是我需要的,都是我所依賴的。一切需要外部提供的,都是需要進行依賴注入的。
我們用代碼來描述一下:
class Boy { protected $girl; public function __construct(Girl $girl) { $this->girl = $girl; }}class Girl { ...}$boy = new Boy(); // Error; Boy must have girlfriend!// so 必須要給他一個女朋友才行 $girl = new Girl();$boy = new Boy($girl); // Right! So Happy!
從上述代碼我們可以看到Boy強依賴Girl必須在構造時注入Girl的執行個體才行。
那麼為什麼要有依賴注入這個概念,依賴注入到底解決了什麼問題?
我們將上述代碼修正一下我們初學時都寫過的代碼:
class Boy { protected $girl; public function __construct() { $this->girl = new Girl(); }}
這種方式與前面的方式有什麼不同呢?
我們會發現Boy的女朋友被我們寫入程式碼到Boy的身體裡去了。。。 每次Boy重生自己想換個類型的女朋友都要把自己扒光才行。。。 (⊙o⊙)…
某天Boy特別喜歡一個LoliGirl ,非常想讓她做自己的女朋友。。。怎麼辦? 重生自己。。。扒開自己。。。把Girl扔了。。。把 LoliGirl塞進去。。。
class LoliGirl {}class Boy { protected $girl; public function __construct() { // $this->girl = new Girl(); // sorry... $this->girl = new LoliGirl(); }}
某天 Boy迷戀上了禦姐.... (⊙o⊙)… Boy 好煩。。。
是不是感覺不太好?每次遇到真心相待的人卻要這麼的折磨自己。。。
Boy說,我要變的強大一點。我不想被改來改去的!
好吧,我們讓Boy強大一點:
interface Girl { // Boy need knows that I have some abilities.}class LoliGril implement Girl { // I will implement Girl's abilities.}class Vixen implement Girl { // Vixen definitely is a girl, do not doubt it.}class Boy { protected $girl; public function __construct(Girl $girl) { $this->girl = $girl; }}$loliGirl = new LoliGirl();$vixen = new Vixen();$boy = new Boy($loliGirl);$boy = new Boy($vixen);
Boy很高興,終於可以不用扒開自己就可以體驗不同的人生了。。。So Happy!
小結
因為大多數應用程式都是由兩個或者更多的類通過彼此合作來實現商務邏輯,這使得每個對象都需要擷取與其合作的對象(也就是它所依賴的對象)的引用。如果這個擷取過程要靠自身實現,那麼將導致代碼高度耦合并且難以維護和調試。
所以才有了依賴注入的概念,依賴注入解決了以下問題:
=。= 前面的依賴注入居然需要我們手動的去注入依賴,做為程式員的我們怎麼可以容忍這種低效的注入方式,好吧,我們先來瞭解一下IOC的概念.
控制反轉 (Inversion Of Control, IOC)
控制反轉 是物件導向編程中的一種設計原則,可以用來減低電腦代碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection, DI), 還有一種叫"依賴尋找"(Dependency Lookup)。通過控制反轉,對象在被建立的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。
也就是說,我們需要一個調控系統,這個調控系統中我們存放一些對象的實體,或者對象的描述,在對象建立的時候將對象所依賴的對象的引用傳遞過去。 在Laravel中Service Container就是這個高效的調控系統,它是laravel的核心。 下面我們看一下laravel是如何?自動依賴注入的。
laravel中的依賴注入
現在我們看文檔給的例子應該就不難理解了:
mailer = $mailer; } /** * Purchase a podcast. * * @return void */ public function handle() { // }}
In this example, the PurchasePodcast job needs to send e-mails when a podcast is purchased. So, we willinject a service that is able to send e-mails. Since the service is injected, we are able to easily swap it out with another implementation. We are also able to easily "mock", or create a dummy implementation of the mailer when testing our application.
說到laravel中的依賴注入,我們不得不瞭解laravel的Service Container
服務容器 (Service Container)
The Laravel service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a fancy phrase that essentially means this: class dependencies are "injected" into the class via the constructor or, in some cases, "setter" methods.
從介紹不難看出服務容器就是控制反轉的容器,它就是前文說到的調度系統。實現依賴注入的方式可以是在建構函式中或者setter方法中。
如果我們仔細研究了Service Container我們就會發現laravel的服務容器中只儲存了對象的描述,而並不需要知道如何具體的去構造一個對象,因為它會根據php的反射服務去自動解析具體化一個對象。
反射
在電腦科學中,反射是指電腦在運行時(Run time)可以訪問、檢測和修改它本身狀態或行為的一種能力。用來比喻說,那種程式能夠“觀察”並且修改自己的行為。
支援反射的語言提供了一些在低級語言中難以實現的運行時特性。這些特性包括
- 作為一個第一類對象發現並修改原始碼的結構(如代碼塊、類、方法、協議等)。
- 將跟class或function匹配的轉換成class或function的調用或引用。
- 在運行時像對待原始碼語句一樣計算字串。
- 建立一個新的語言位元組碼解譯器來給編程結構一個新的意義或用途。
PHP實現的反射可以在官網文檔中進行查看: 反射API
Example
$reflector = new ReflectionClass('App\User');if ($reflector->isInstantiable()) { $user = $refector->newInstance(); //in other case you can send any arguments}
laravel的服務容器的build方法中需要通過反射服務來解析依賴關係,比如說construct函數中需要傳遞的依賴參數有哪些? 它就需要用到如下方法:
$constructor = $reflector->getConstructor(); // If there are no constructors, that means there are no dependencies then // we can just resolve the instances of the objects right away, without // resolving any other types or dependencies out of these containers. if (is_null($constructor)) { array_pop($this->buildStack); return new $concrete; } $dependencies = $constructor->getParameters();
現在我們應該對laravel如何?依賴的自動注入有點想法了吧?來整理一下疑問:
- 如何?依賴的自動注入? (控制反轉,利用反射)
- 依賴注入需要哪些東東? (整理依賴關係[ construct | setter ],還要解析依賴傳遞引用)
- 怎麼解析依賴?
你可能會問為什麼要問怎麼解析依賴?解析依賴肯定是要用到反射的啦,反射,你知道類名不就可以直接解析了嗎?
其實。。。不是這樣的。。。(@ο@)
很多時候我們為了提高代碼的擴充性和維護性,在編寫類時依賴的是介面或抽象類別,而並不是一個具體的實作類別。明白了嗎?依賴解析的時候如果只解析到介面或抽象類別,然後利用反射,那麼這個依賴肯定是錯誤的。
那麼我們就需要在調度系統中注入相關依賴的映射關係,然後在需要的時候正確的解析關係。 比如說, 喂, 我需要一個 A, 你別給我 B 啊。
$container->bind('a', function () { return new B(); // just this for you});$a = $container->make('a');
總結
- 依賴注入是控制反轉的一種實現,實現代碼解耦,便於單元測試。因為它並不需要瞭解自身所依賴的類,而只需要知道所依賴的類實現了自身所需要的方法就可以了。你需要我,你卻不認識我/(ㄒoㄒ)/~~
- 控制反轉提供一種調控系統,實現依賴解析的自動注入,一般配合容器提供依賴對象執行個體的引用。