前言
想要看懂源碼首先得明白源碼中的主要思想,不然很難理解源碼中的實(shí)現(xiàn)過程凉袱,本章就著重講解Laravel框架中的服務(wù)容器芥吟、依賴注入、控制反轉(zhuǎn)专甩、服務(wù)提供者
正文
首先我們舉個(gè)例子
假設(shè)我們要開始一場(chǎng)旅行钟鸵,以代碼的方式來實(shí)現(xiàn)這場(chǎng)旅行,那既然是旅行肯定要選擇交通工具配深,我們用代碼這樣實(shí)現(xiàn):
//火車類
class Train
{
//買票
function buyTickets()
{
}
//出發(fā)
function go()
{
}
}
//旅行類
class Travel
{
//出發(fā)
function go()
{
//選擇火車
$train = new Train();
$train->buyTickets();
$train->go();
}
}
我們通常都是以這種方式實(shí)現(xiàn)代碼的結(jié)構(gòu)携添,清晰嫁盲、方便實(shí)現(xiàn)篓叶,但是如果我們又要旅行一次但這次我們不坐火車想坐飛機(jī)
那這時(shí)我們就需要修改旅行類的go方法(調(diào)用者),將實(shí)例化火車這個(gè)步驟修改為實(shí)例化飛機(jī)然后go~
//飛機(jī)類
class Aircraft
{
//買票
function buyTickets()
{
}
//出發(fā)
function go()
{
}
}
//旅行類
class Travel
{
//出發(fā)
function go()
{
//選擇飛機(jī)
$train = new Aircraft();
$train->buyTickets();
$train->go();
}
}
這種情況我們的調(diào)用者(旅游類)就依賴于被調(diào)用者(火車羞秤,飛機(jī)類)變成了強(qiáng)耦合缸托,如果我們要旅行好幾次,或者說我們不止是旅行瘾蛋,我們出差時(shí)也要用到這些交通工具相關(guān)的類俐镐,那我們?cè)侔鸦疖囶惛鼡Q為飛機(jī)類的時(shí)候是不是需要修改很多調(diào)用者呢?
工廠模式
這時(shí)我們?yōu)榱吮苊膺@種情況發(fā)生哺哼,引入了工廠類
//創(chuàng)建工廠類
class TrafficFactory
{
public function getInstance()
{
//第一次旅行使用火車
// $tool = new Train();
//第二次旅行使用飛機(jī)
$tool = new Aircraft();
return $tool;
}
}
//旅行類
class Travel
{
//出發(fā)
function go()
{
//獲取工廠類
$factory = new TrafficFactory();
//獲取這次的交通工具
$tool = $factory->getInstance();
//買票
$tool->buyTickets();
//出發(fā)
$tool->go();
}
}
這下我們不管旅行幾次換成什么交通工具我們都只需要去修改工廠類就可以了佩抹,而且我們的調(diào)用者也不再依賴于被調(diào)用者
但實(shí)際上需求總是那么的可怕,這次要新增的交通工具是汽車取董,自駕不需要買票(buyTickets)棍苹,但是要自己去加油,我們需要先創(chuàng)建汽車類
class Car
{
function gasUp()
{
}
function go()
{
}
}
我們一樣可以將汽車類加到工廠類中茵汰,但是我們?cè)谑褂脮r(shí)并不知道工廠給我們的是需要買票的飛機(jī)和火車還是需要加油的汽車
接口
這時(shí)就引入的接口類
interface Traffic
{
//準(zhǔn)備枢里,不管有什么前置使用條件(加油,買票)都需要再次方法進(jìn)行實(shí)現(xiàn)
public function ready();
//出發(fā)
public function go();
}
所有有關(guān)交通工具的類都需要遵守規(guī)則實(shí)現(xiàn)接口里的方法,所以進(jìn)行以下改造(這里為了不貼太多代碼我們只改造汽車類)
class Car implements Traffic
{
function ready()
{
$this->gasUp();
}
function gasUp()
{
}
function go()
{
}
}
這樣我們就將交通工具的使用方法給統(tǒng)一了
現(xiàn)在我們的調(diào)用者不直接依賴于被調(diào)用者栏豺,但是我們卻依賴于工廠類了彬碱,有什么辦法這個(gè)調(diào)用者與工廠進(jìn)行解耦呢?
依賴注入(DI)&控制反轉(zhuǎn)(IOC)
調(diào)用者不需要去關(guān)心用的是什么交通工具奥洼,也不需要知道要做什么準(zhǔn)備我們只管go巷疼,以依賴注入&控制反轉(zhuǎn)的思想進(jìn)行如下改造
class Travel
{
//出發(fā)
function go(Traffic $tool)
{
//出發(fā)
$tool->go();
}
}
//獲取交通工具工廠類
$factory = new TrafficFactory();
//獲取這次的交通工具
$tool = $factory->getInstance();
$travel = new Travel();
//準(zhǔn)備
$tool->ready();
$travel->go($tool);
這下調(diào)用者只管go,外面做什么他都不用管溉卓,就算是更換了其它交通工具甚至是更換了工廠也不關(guān)調(diào)用者的事了皮迟。
起先我們獲取交通工具是在調(diào)用者中做的,現(xiàn)在放到外部去了桑寨,這時(shí)控制權(quán)就不在調(diào)用者內(nèi)部這種變化稱為控制反轉(zhuǎn)伏尼,我們以傳參的形式將調(diào)用者所需的服務(wù)(交通工具)注入給調(diào)用者使用,這種行為稱為依賴注入
接下來隨著業(yè)務(wù)增多尉尾,需求不斷變化(上面的例子全是交通工具相關(guān)爆阶,旅行還會(huì)涉及用餐,住宿等等)沙咏,因?yàn)楣S類通常職責(zé)專一(也是面向?qū)ο蟮闹饕枷胫唬┪覀冃枰墓S也會(huì)越來越多工廠類變得越來越多
再者上面提到的交通工具辨图,用餐,住宿并不只適用與旅行肢藐,我們還可能出差等等故河,我們?cè)趯?shí)現(xiàn)這些功能時(shí)我們都需要提前準(zhǔn)備好調(diào)用者所需的依賴
有沒什么辦法可以改進(jìn)上面兩點(diǎn)呢?
服務(wù)容器(IOC容器)
這時(shí)服務(wù)容器就出現(xiàn)了吆豹,我們將上述例子中的交通工具鱼的,用餐,住宿統(tǒng)稱為服務(wù)痘煤,將服務(wù)統(tǒng)一收納起來凑阶,存放的地方就是服務(wù)容器,可以簡(jiǎn)單理解為在框架啟動(dòng)時(shí)將以上服務(wù)提前存入服務(wù)容器中的數(shù)組中衷快,在使用時(shí)去數(shù)組中獲取宙橱,下面進(jìn)行簡(jiǎn)單的實(shí)現(xiàn)
//-------------框架啟動(dòng)開始-------------
//服務(wù)容器
class Container
{
//存放位置
protected $bindings;
//對(duì)服務(wù)進(jìn)行綁定
public function bind($abstract, $concrete)
{
$this->bindings[$abstract] = $concrete;
}
//制造服務(wù)
public function make($abstract)
{
return $this->bindings[$abstract];
}
}
//創(chuàng)建容器
$container = new Container();
//以閉包的形式(后續(xù)會(huì)將為何閉包)注冊(cè)服務(wù)
$container->bind('tool', function(){
//以汽車為例
return new Car();
});
//-------------框架啟動(dòng)結(jié)束-------------
class Travel
{
//出發(fā)
function go(Traffic $tool)
{
//出發(fā)
$tool->go();
}
}
//獲取交通工具工廠類
$tool = $container->make('tool');
$travel = new Travel();
//閉包在使用的時(shí)候要先進(jìn)行調(diào)用所以這里是$tool()而不是直接將$tool傳入
$travel->go($tool());
這樣我們只需要在框架啟動(dòng)過程中就將所有可能用到的服務(wù)進(jìn)行注冊(cè)即可,使用時(shí)再去容器中獲取
在上面在對(duì)服務(wù)進(jìn)行綁定的時(shí)候使用了閉包的寫法避免了在綁定的過程中直接對(duì)類進(jìn)行實(shí)例化蘸拔,這樣就減少了框架啟動(dòng)時(shí)的開銷
具體的原理在這篇文章有提到:PHP Closure::bind方法理解及使用
上面舉得例子只是對(duì)交通工具進(jìn)行注冊(cè)师郑,但如果進(jìn)行注冊(cè)的服務(wù)有很多就會(huì)有一大堆服務(wù)雜亂無章的堆積在框架啟動(dòng)時(shí)的地方
服務(wù)提供者
這時(shí)服務(wù)提供者就來了
服務(wù)容器是將具體的服務(wù)進(jìn)行注冊(cè)收納及解析使用,通常一些不集中的服務(wù)都是通過在框架各個(gè)位置進(jìn)行注冊(cè)调窍,而服務(wù)提供者則是將與自身相關(guān)或依賴的服務(wù)進(jìn)行集合來注冊(cè)宝冕,類似交通工具,住宿之類的服務(wù)都會(huì)創(chuàng)建與之相關(guān)的交通工具服務(wù)提供者陨晶,住宿服務(wù)提供者猬仁,在具體的服務(wù)提供者類內(nèi)部進(jìn)行集合注冊(cè)帝璧,和一些前置處理,好處就是相關(guān)的服務(wù)可以用放在一起進(jìn)行集中管理
有沒發(fā)現(xiàn)這樣又有點(diǎn)像是工廠模式了湿刽,創(chuàng)建相關(guān)的服務(wù)提供者就像創(chuàng)建相關(guān)的工廠一樣的烁。其實(shí)項(xiàng)目演變就是這樣我們拋棄了相對(duì)較小工廠模式選擇了較大的“超級(jí)工廠”服務(wù)容器,服務(wù)容器壯大的同時(shí)又需要服務(wù)提供者來進(jìn)行分類管理诈闺。
下面進(jìn)行簡(jiǎn)單實(shí)現(xiàn)
//交通工具服務(wù)提供者
class ToolServiceProvider
{
//統(tǒng)一注冊(cè)
public function register()
{
//創(chuàng)建容器
$container = new Container();
$container->bind('tool', function () {
//以汽車為例
return new Car();
});
}
}
//交通工具服務(wù)提供者
$toolServiceProvider = new ToolServiceProvider();
$toolServiceProvider->register();
//用餐服務(wù)提供者(省略代碼未貼具體實(shí)現(xiàn)代碼)
$eatServiceProvider = new EatServiceProvider();
$eatServiceProvider->register();
//-------------框架啟動(dòng)結(jié)束-------------
class Travel
{
//出發(fā)
function go(Traffic $tool)
{
//出發(fā)
$tool->go();
}
}
//獲取交通工具工廠類
$tool = $container->make('tool');
$travel = new Travel();
$travel->go($tool);
本篇文章只是進(jìn)行思想解讀渴庆,上述所有代碼實(shí)現(xiàn)在實(shí)際運(yùn)用場(chǎng)景中都不僅僅是這么簡(jiǎn)單,特別是服務(wù)容器與服務(wù)提供者雅镊,在Laravel框架底層中的實(shí)現(xiàn)過程還是較為復(fù)雜的襟雷,但在使用方面還是十分優(yōu)雅,不會(huì)像上面的例子為了節(jié)省代碼顯得有點(diǎn)生搬硬套仁烹。
END