思想
思想是解決問題的根本思想必須轉(zhuǎn)換成習(xí)慣構(gòu)建一套完整的思想體系是開發(fā)能力成熟的標(biāo)志——《簡(jiǎn)單之美》(前言)
“成功的軟件項(xiàng)目就是那些提交產(chǎn)物達(dá)到或超出客戶的預(yù)期的項(xiàng)目,而且開發(fā)過程符合時(shí)間和費(fèi)用上的要求介时,結(jié)果在面對(duì)變化和調(diào)整時(shí)有彈性燎孟∶芮浚”——《面向?qū)ο蠓治雠c設(shè)計(jì)》(第3版)P.236
術(shù)語介紹
——引用《Spring 2.0 技術(shù)手冊(cè)》林信良
非侵入性 No intrusive
框架的目標(biāo)之一是非侵入性(No intrusive)
組件可以直接拿到另一個(gè)應(yīng)用或框架之中使用
增加組件的可重用性(Reusability)
容器(Container)
管理對(duì)象的生成尔许、資源取得购桑、銷毀等生命周期
建立對(duì)象與對(duì)象之間的依賴關(guān)系
啟動(dòng)容器后膀斋,所有對(duì)象直接取用茵典,不用編寫任何一行代碼來產(chǎn)生對(duì)象,或是建立對(duì)象之間的依賴關(guān)系医窿。
IoC
控制反轉(zhuǎn) Inversion of Control
依賴關(guān)系的轉(zhuǎn)移
依賴抽象而非實(shí)踐
DI
依賴注入 Dependency Injection
不必自己在代碼中維護(hù)對(duì)象的依賴
容器自動(dòng)根據(jù)配置磅甩,將依賴注入指定對(duì)象
AOP
Aspect-oriented programming
面向方面編程
無需修改任何一行程序代碼,將功能加入至原先的應(yīng)用程序中姥卢,也可以在不修改任何程序的情況下移除卷要。
分層
表現(xiàn)層:提供服務(wù),顯示信息独榴。領(lǐng)域?qū)樱哼壿嬌妫到y(tǒng)中真正的核心。數(shù)據(jù)源層:與數(shù)據(jù)庫棺榔、消息系統(tǒng)瓶堕、事務(wù)管理器及其它軟件包通信≈⑿——《企業(yè)應(yīng)用架構(gòu)模式》P.14
代碼演示IoC
假設(shè)應(yīng)用程序有儲(chǔ)存需求郎笆,若直接在高層的應(yīng)用程序中調(diào)用低層模塊API谭梗,導(dǎo)致應(yīng)用程序?qū)Φ蛯幽K產(chǎn)生依賴。
/**
* 高層
*/
class Business
{
private $writer;
public function __construct()
{
$this->writer = new FloppyWriter();
}
public function save()
{
$this->writer->saveToFloppy();
}
}
/**
* 低層题画,軟盤存儲(chǔ)
*/
class FloppyWriter
{
public function saveToFloppy()
{
echo __METHOD__;
}
}
$biz = new Business();
$biz->save(); // FloppyWriter::saveToFloppy
假設(shè)程序要移植到另一個(gè)平臺(tái),而該平臺(tái)使用USB磁盤作為存儲(chǔ)介質(zhì)德频,則這個(gè)程序無法直接重用苍息,必須加以修改才行。本例由于低層變化導(dǎo)致高層也跟著變化壹置,不好的設(shè)計(jì)竞思。
正如前方提到的
控制反轉(zhuǎn) Inversion of Control
依賴關(guān)系的轉(zhuǎn)移
依賴抽象而非實(shí)踐
程序不應(yīng)該依賴于具體的實(shí)現(xiàn),而是要依賴抽像的接口钞护。請(qǐng)看代碼演示
/**
* 接口
*/
interface IDeviceWriter
{
public function saveToDevice();
}
/**
* 高層
*/
class Business
{
/**
* @var IDeviceWriter
*/
private $writer;
/**
* @param IDeviceWriter $writer
*/
public function setWriter($writer)
{
$this->writer = $writer;
}
public function save()
{
$this->writer->saveToDevice();
}
}
/**
* 低層盖喷,軟盤存儲(chǔ)
*/
class FloppyWriter implements IDeviceWriter
{
public function saveToDevice()
{
echo __METHOD__;
}
}
/**
* 低層,USB盤存儲(chǔ)
*/
class UsbDiskWriter implements IDeviceWriter
{
public function saveToDevice()
{
echo __METHOD__;
}
}
$biz = new Business();
$biz->setWriter(new UsbDiskWriter());
$biz->save(); // UsbDiskWriter::saveToDevice
$biz->setWriter(new FloppyWriter());
$biz->save(); // FloppyWriter::saveToDevice
控制權(quán)從實(shí)際的FloppyWriter轉(zhuǎn)移到了抽象的IDeviceWriter接口上难咕,讓Business依賴于IDeviceWriter接口课梳,且FloppyWriter、UsbDiskWriter也依賴于IDeviceWriter接口余佃。
這就是IoC暮刃,面對(duì)變化,高層不用修改一行代碼爆土,不再依賴低層椭懊,而是依賴注入,這就引出了DI步势。
比較實(shí)用的注入方式有三種:
1.Setter injection 使用setter方法
2.Constructor injection 使用構(gòu)造函數(shù)
3.Property Injection 直接設(shè)置屬性
事實(shí)上不管有多少種方法氧猬,都是IoC思想的實(shí)現(xiàn)而已,上面的代碼演示的是Setter方式的注入坏瘩。
依賴注入容器 Dependency Injection Container
1.管理應(yīng)用程序中的『全局』對(duì)象(包括實(shí)例化盅抚、處理依賴關(guān)系)。
2.可以延時(shí)加載對(duì)象(僅用到時(shí)才創(chuàng)建對(duì)象)倔矾。
3.促進(jìn)編寫可重用泉哈、可測(cè)試和松耦合的代碼。
理解了IoC和DI之后破讨,就引發(fā)了另一個(gè)問題丛晦,引用Phalcon文檔描述如下:
如果這個(gè)組件有很多依賴, 我們需要?jiǎng)?chuàng)建多個(gè)參數(shù)的setter方法??來傳遞依賴關(guān)系提陶,或者建立一個(gè)多個(gè)參數(shù)的構(gòu)造函數(shù)來傳遞它們烫沙,另外在使用組件前還要每次都創(chuàng)建依賴,這讓我們的代碼像這樣不易維護(hù)
//創(chuàng)建依賴實(shí)例或從注冊(cè)表中查找
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
//把實(shí)例作為參數(shù)傳遞給構(gòu)造函數(shù)
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... 或者使用setter
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
假設(shè)我們必須在應(yīng)用的不同地方使用和創(chuàng)建這些對(duì)象隙笆。如果當(dāng)你永遠(yuǎn)不需要任何依賴實(shí)例時(shí)锌蓄,你需要去刪掉構(gòu)造函數(shù)的參數(shù)升筏,或者去刪掉注入的setter。為了解決這樣的問題瘸爽,我們?cè)俅位氐饺肿?cè)表創(chuàng)建組件您访。不管怎么樣,在創(chuàng)建對(duì)象之前剪决,它增加了一個(gè)新的抽象層:
class SomeComponent
{
// ...
/**
* Define a factory method to create SomeComponent instances injecting its dependencies
*/
public static function factory()
{
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
return new self($connection, $session, $fileSystem, $filter, $selector);
}
}
瞬間灵汪,我們又回到剛剛開始的問題了,我們?cè)俅蝿?chuàng)建依賴實(shí)例在組件內(nèi)部柑潦!我們可以繼續(xù)前進(jìn)享言,找出一個(gè)每次能奏效的方法去解決這個(gè)問題。但似乎一次又一次渗鬼,我們又回到了不實(shí)用的例子中览露。
一個(gè)實(shí)用和優(yōu)雅的解決方法,是為依賴實(shí)例提供一個(gè)容器譬胎。這個(gè)容器擔(dān)任全局的注冊(cè)表差牛,就像我們剛才看到的那樣。使用依賴實(shí)例的容器作為一個(gè)橋梁來獲取依賴實(shí)例堰乔,使我們能夠降低我們的組件的復(fù)雜性:
class SomeComponent
{
protected $_di;
public function __construct($di)
{
$this->_di = $di;
}
public function someDbTask()
{
// 獲得數(shù)據(jù)庫連接實(shí)例
// 總是返回一個(gè)新的連接
$connection = $this->_di->get('db');
}
public function someOtherDbTask()
{
// 獲得共享連接實(shí)例
// 每次請(qǐng)求都返回相同的連接實(shí)例
$connection = $this->_di->getShared('db');
// 這個(gè)方法也需要一個(gè)輸入過濾的依賴服務(wù)
$filter = $this->_di->get('filter');
}
}
$di = new Phalcon\DI();
//在容器中注冊(cè)一個(gè)db服務(wù)
$di->set('db', function() {
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
});
//在容器中注冊(cè)一個(gè)filter服務(wù)
$di->set('filter', function() {
return new Filter();
});
//在容器中注冊(cè)一個(gè)session服務(wù)
$di->set('session', function() {
return new Session();
});
//把傳遞服務(wù)的容器作為唯一參數(shù)傳遞給組件
$some = new SomeComponent($di);
$some->someTask();
這個(gè)組件現(xiàn)在可以很簡(jiǎn)單的獲取到它所需要的服務(wù)多糠,服務(wù)采用延遲加載的方式,只有在需要使用的時(shí)候才初始化浩考,這也節(jié)省了服務(wù)器資源夹孔。這個(gè)組件現(xiàn)在是高度解耦。例如析孽,我們可以替換掉創(chuàng)建連接的方式搭伤,它們的行為或它們的任何其他方面,也不會(huì)影響該組件袜瞬。
參考文章
PHP程序員如何理解依賴注入容器(dependency injection container)
http://docs.phalconphp.com/zh/latest/reference/di.html
What is Dependency Injection? Fabien Potencier
Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler