服務容器簡介服務容器是 Laravel 非常核心的內容,也可以說是 Laravel 中最引人注目的地方。提到服務容器,就不得不提到一大堆高大上的名詞,依賴注入、控制反轉、依賴倒置、反射等等。要了解 Laravel 是怎么實現(xiàn)服務容器的,也要先從這些名詞入手,我們就一個一個地來看看。 依賴、依賴注入與控制反轉依賴,指的是一個類 A 的變化會引起另一個類 B 的變化,我們就說這個類 B 依賴于類 A 。還是用例子來說明會更清晰。 假設我們閑著沒事了,想刷短視頻,那么我們定義一個類,代表“我”,有個方法就叫“刷短視頻”。
主體類有了,里面要寫什么呢?我們要刷視頻,那得有個手機對吧,于是我們再定義一個手機類,高大上一點,就是一個 iPhone12 吧。
好了,接下來我們讓刷短視頻的動作去打開 App ,也就是調用手機類的 openApp() 方法。
功能完成了,高興不,完美不,一切都是那么美好,但是,注意,這里但是來了就說明沒啥好事了。我們想換一個手機,或者原來的手機壞了,這時候要怎么辦呢?一個方法是直接修改 iPhone12 類,把它變成另一個手機,另一個方法是重新定義一個手機類,我們采用第二個方法。
我們又定義了一個小米手機,因為要換手機,所以我們的 iPhone12 要產(chǎn)生變化(類A變了),于是,我們的這個個體類(類B)也要跟著修改,這就是 依賴 。接下來,我們思考能不能讓類A變化,類B盡量不動呢?這就需要先抽象類A,它們有一個相同的方法都是打開 App ,那么我們定義一個接口或者抽象類。在這里其實抽象類更合適,因為代碼非常簡單 openApp() 方法的內部實現(xiàn)是一樣的。不過,更推薦的方式其實是定義一個接口,因為很有可能你的手機是老年功能機,或許根本沒有 App 這個概念,那么老年機就不要去實現(xiàn)這個接口就好了。于是,我們就定義一個抽象的手機接口,然后讓兩個類去實現(xiàn)這個接口。
然后,我們秉承接口的特性,來實現(xiàn)刷視頻的效果。
看出來這個問題是如何解決的嗎?沒錯,我們現(xiàn)在其實還有依賴,但是,我們的依賴是通過方法的參數(shù)注入過來的,這就叫做 依賴注入 ?,F(xiàn)在我們還需要修改個體類中刷短視頻的實現(xiàn)嗎?不需要了,我們將手機類對象的控制權交給了外部,這個外部就像是一個第三方,你的一個同學或者朋友,看見你拿什么手機在刷短視頻,取決于他在 當下真實的看到 你手上拿的是什么手機。這樣控制權轉移到類實現(xiàn)的外部,就叫做 控制反轉 。 從上面的例子其實可以出,依賴注入和控制反轉其實是可以理解成一回事的,根據(jù)依賴的定義,如果想要讓 類B 不再依賴 類A ,那么就得把實例化 類A 的控制權交到外部去,這其實就是一個控制反轉的過程,而交到外部實例化之后的對象必須要想辦法再交給 類B 去使用,這就是一個依賴注入的過程。 這些概念在技術實現(xiàn)層面上,其實就是利用的面向對象的封裝、繼承、多態(tài)、接口實現(xiàn)這些特性。目的嘛,當然就是了為那個 開放封閉原則 。要知道,代碼在修改維護時是最容易出現(xiàn)問題的,而接口的契約性就能讓你必須在規(guī)則范圍之內去實現(xiàn),因此,具體的調用方就可以放心地使用接口中的方法。 依賴注入的簡稱學名是 DI ,控制反轉的簡稱學名是 IoC 。 服務容器 IoC Container弄明白上面兩個問題,那么 服務容器 這個概念其實也就很好理解了。從英文簡寫就可以看出,其實服務容器也就是一個控制返轉容器。既然是容器,那么它其實就是保存我們所有需要要依賴的對象。
這是網(wǎng)上非常經(jīng)典的一段服務容器的代碼實現(xiàn),具體的原文鏈接還是在下方的參考鏈接處。從這段代碼中,我們就可以看出容器無非就是兩個數(shù)組,一個數(shù)組用于存儲綁定的 回調函數(shù) 操作。另一個數(shù)組用于存儲實例化之后的對象操作。 在 bind() 方法中,我們有兩種對象接收方式。一種是使用回調函數(shù),將這些回調函數(shù)放入到 binds 數(shù)組中。而另一種是直接的實例對象,實例對象放到 instances 數(shù)組中。當我們使用 make() 方法的時候,如果這個服務對象是一個具體的實例化對象,那么我們直接從 instances 中返回對應的對象就可以啦,如果我們之前綁定的是一個閉包函數(shù),那么我們就去執(zhí)行這個閉包函數(shù)。 為什么要使用閉包函數(shù)呢?其實閉包函數(shù)有一個非常大的好處就是可以 推后實例化 或者叫 延遲實例化。服務容器的核心思想是將所有依賴,也就是 new 實例化的工作統(tǒng)一管理起來。如果說我們全都是直接綁定實例化的對象,那么像 Laravel 這種級別的框架就可能在啟動的時候就會占用大量的內存,因為我們需要將所有需要用到的對象都實例化出來。明顯這是不現(xiàn)實的。而通過回調函數(shù),我們就可以實現(xiàn)在需要的時候去實例化,而不用提前將大量的對象實例化好。
就像這段代碼,我們完全可以在程序一開頭就定義好各種回調函數(shù),在這個時候回調函數(shù)沒有調用過,里面的 new 并沒有創(chuàng)建實例對象。然后在需要某個對象的時候,直接去調用這個回調函數(shù)來獲得所需的對象。相信到這里你已經(jīng)明白回調函數(shù)是個什么作用了吧。接下來,我們再細想一下,服務容器是在創(chuàng)建返回對象,而且還通過一個回調函數(shù)的方式延遲返回,好好回想一下,這不是一個工廠方法嘛??! 沒錯,如果要按設計模式來說的話,它真是屬于一個創(chuàng)建型模式。要按親屬關系來看,它和 工廠方法 還真的非常親近,另外,還有一個設計模式不知道你發(fā)現(xiàn)了沒有。如果是 bind() 一個實例化對象的話,我們返回的會是一個全局唯一的對象,也就是一個 單例模式 或者是一個 享元模式 的實現(xiàn),是不是非常非常像。OK,轉化一下思路,一個工廠方法模式加上享元模式中的那個 factory 方法,是不是就成了一個服務容器了??磥碇暗闹R真的是沒有白學啊。 我們先使用回調函數(shù)的方式來應用服務容器。
注意我們最后打印了兩次 make() 出來的對象。由于每次調用回調函數(shù)都會創(chuàng)建一個新的對象,因此,make() 每次返回的都會是一個新的對象,它們肯定是不會全等的。接下來我們使用實例化的方式來加入到服務容器中。
很明顯,在我們的服務容器中使用實例化的對象方式之后,對象會保存在 instances 中,也就是說它們都只會保存一份實例化對象,這時就是一種 單例 或者更像 享元 的形式,返回的是同一個對象了。 總結最后我們總結一下,服務容器,真的就是個容器,這個容器里面放的是什么呢?就是我們需要的各種對象。它為了解決什么問題呢?解決的就是我們的依賴問題。夠了,有這些相信你應對普通的面試問題不大了。需要背下來嗎?這么簡單一個容器類估計看兩遍就記住了吧。另外我們還講了一下回調函數(shù)來生產(chǎn)對象的好處以及與設計模式之間的相似性。 代碼示例中的上半部分,也就是人和手機的部分是我自己寫的,主要就是講依賴注入這塊臨時想到的。個人感覺例子沒有下面鏈接中大神的 超人 例子好,這個超人例子講解服務容器非常經(jīng)典,也是我很早就看到過的文章。強烈推薦大家放到收藏夾中不斷回味學習。 參考文檔: laravel 學習筆記 —— 神奇的服務容器:https://blog.csdn.net/CouryLove/article/details/107665507 |
|
來自: 硬核項目經(jīng)理 > 《待分類》