W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
看Laravel的IoC容器文檔只是介紹實(shí)例,但是沒(méi)有說(shuō)原理,之前用MVC框架都沒(méi)有在意這個(gè)概念,無(wú)意中在phalcon的文檔中看到這個(gè)詳細(xì)的介紹,感覺(jué)豁然開(kāi)朗,復(fù)制粘貼過(guò)來(lái),主要是好久沒(méi)有寫(xiě)東西了,現(xiàn)在確實(shí)很懶變得!
首先,我們假設(shè),我們要開(kāi)發(fā)一個(gè)組件命名為SomeComponent。這個(gè)組件中現(xiàn)在將要注入一個(gè)數(shù)據(jù)庫(kù)連接。
在這個(gè)例子中,數(shù)據(jù)庫(kù)連接在component中被創(chuàng)建,這種方法是不切實(shí)際的,這樣做的話,我們將不能改變數(shù)據(jù)庫(kù)連接參數(shù)及數(shù)據(jù)庫(kù)類型等一些參數(shù)。
<?php
class SomeComponent
{
/**
* The instantiation of the connection is hardcoded inside
* the component so is difficult to replace it externally
* or change its behavior
*/
public function someDbTask()
{
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
// ...
}
}
$some = new SomeComponent();
$some->someDbTask();
為了解決上面所說(shuō)的問(wèn)題,我們需要在使用前創(chuàng)建一個(gè)外部連接,并注入到容器中。就目前而言,這看起來(lái)是一個(gè)很好的解決方案:
<?php
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection)
{
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
//Create the connection
$connection = new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
//Inject the connection in the component
$some->setConnection($connection);
$some->someDbTask();
現(xiàn)在我們來(lái)考慮一個(gè)問(wèn)題,我們?cè)趹?yīng)用程序中的不同地方使用此組件,將多次創(chuàng)建數(shù)據(jù)庫(kù)連接。使用一種類似全局注冊(cè)表的方式,從這獲得一個(gè)數(shù)據(jù)庫(kù)連接實(shí)例,而不是使用一次就創(chuàng)建一次。
<?php
class Registry
{
/**
* Returns the connection
*/
public static function getConnection()
{
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
}
}
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection){
$this->_connection = $connection;
}
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
}
$some = new SomeComponent();
//Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());
$some->someDbTask();
現(xiàn)在,讓我們來(lái)想像一下,我們必須在組件中實(shí)現(xiàn)兩個(gè)方法,首先需要?jiǎng)?chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)連接,第二個(gè)總是獲得一個(gè)共享連接:
<?php
class Registry
{
protected static $_connection;
/**
* Creates a connection
*/
protected static function _createConnection()
{
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
}
/**
* Creates a connection only once and returns it
*/
public static function getSharedConnection()
{
if (self::$_connection===null){
$connection = self::_createConnection();
self::$_connection = $connection;
}
return self::$_connection;
}
/**
* Always returns a new connection
*/
public static function getNewConnection()
{
return self::_createConnection();
}
}
class SomeComponent
{
protected $_connection;
/**
* Sets the connection externally
*/
public function setConnection($connection){
$this->_connection = $connection;
}
/**
* This method always needs the shared connection
*/
public function someDbTask()
{
$connection = $this->_connection;
// ...
}
/**
* This method always needs a new connection
*/
public function someOtherDbTask($connection)
{
}
}
$some = new SomeComponent();
//This injects the shared connection
$some->setConnection(Registry::getSharedConnection());
$some->someDbTask();
//Here, we always pass a new connection as parameter
$some->someOtherDbTask(Registry::getConnection());
到此為止,我們已經(jīng)看到了如何使用依賴注入解決我們的問(wèn)題。不是在代碼內(nèi)部創(chuàng)建依賴關(guān)系,而是讓其作為一個(gè)參數(shù)傳遞,這使得我們的程序更容易維護(hù),降低程序代碼的耦合度,實(shí)現(xiàn)一種松耦合。但是從長(zhǎng)遠(yuǎn)來(lái)看,這種形式的依賴注入也有一些缺點(diǎn)。
例如,如果組件中有較多的依賴關(guān)系,我們需要?jiǎng)?chuàng)建多個(gè)setter方法傳遞,或創(chuàng)建構(gòu)造函數(shù)進(jìn)行傳遞。另外,每次使用組件時(shí),都需要?jiǎng)?chuàng)建依賴組件,使代碼維護(hù)不太易,我們編寫(xiě)的代碼可能像這樣:
<?php
//Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();
//Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
// ... or using setters
$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);
我想,我們不得不在應(yīng)用程序的許多地方創(chuàng)建這個(gè)對(duì)象。如果你不需要依賴的組件后,我們又要去代碼注入部分移除構(gòu)造函數(shù)中的參數(shù)或者是setter方法。為了解決這個(gè)問(wèn)題,我們?cè)俅畏祷厝ナ褂靡粋€(gè)全局注冊(cè)表來(lái)創(chuàng)建組件。但是,在創(chuàng)建對(duì)象之前,它增加了一個(gè)新的抽象層:
<?php
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);
}
}
這一刻,我們好像回到了問(wèn)題的開(kāi)始,我們正在創(chuàng)建組件內(nèi)部的依賴,我們每次都在修改以及找尋一種解決問(wèn)題的辦法,但這都不是很好的做法。
一種實(shí)用和優(yōu)雅的來(lái)解決這些問(wèn)題,是使用容器的依賴注入,像我們?cè)谇懊婵吹降?,容器作為全局注?cè)表,使用容器的依賴注入做為一種橋梁來(lái)解決依賴可以使我們的代碼耦合度更低,很好的降低了組件的復(fù)雜性:
<?php
class SomeComponent
{
protected $_di;
public function __construct($di)
{
$this->_di = $di;
}
public function someDbTask()
{
// Get the connection service
// Always returns a new connection
$connection = $this->_di->get('db');
}
public function someOtherDbTask()
{
// Get a shared connection service,
// this will return the same connection everytime
$connection = $this->_di->getShared('db');
//This method also requires a input filtering service
$filter = $this->_db->get('filter');
}
}
$di = new Phalcon\DI();
//Register a "db" service in the container
$di->set('db', function(){
return new Connection(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "invo"
));
});
//Register a "filter" service in the container
$di->set('filter', function(){
return new Filter();
});
//Register a "session" service in the container
$di->set('session', function(){
return new Session();
});
//Pass the service container as unique parameter
$some = new SomeComponent($di);
$some->someTask();
現(xiàn)在,該組件只有訪問(wèn)某種service的時(shí)候才需要它,如果它不需要,它甚至不初始化,以節(jié)約資源。該組件是高度解耦。他們的行為,或者說(shuō)他們的任何其他方面都不會(huì)影響到組件本身。
我們的實(shí)現(xiàn)辦法?
Phalcon\DI 是一個(gè)實(shí)現(xiàn)了服務(wù)的依賴注入功能的組件,它本身也是一個(gè)容器。
由于Phalcon高度解耦,Phalcon\DI 是框架用來(lái)集成其他組件的必不可少的部分,開(kāi)發(fā)人員也可以使用這個(gè)組件依賴注入和管理應(yīng)用程序中不同類文件的實(shí)例。
基本上,這個(gè)組件實(shí)現(xiàn)了 Inversion of Control 模式。基于此,對(duì)象不再以構(gòu)造函數(shù)接收參數(shù)或者使用setter的方式來(lái)實(shí)現(xiàn)注入,而是直接請(qǐng)求服務(wù)的依賴注入。這就大大降低了整體程序的復(fù)雜性,因?yàn)橹挥幸粋€(gè)方法用以獲得所需要的一個(gè)組件的依賴關(guān)系。
此外,這種模式增強(qiáng)了代碼的可測(cè)試性,從而使它不容易出錯(cuò)。
在容器中注冊(cè)服務(wù)?
框架本身或開(kāi)發(fā)人員都可以注冊(cè)服務(wù)。當(dāng)一個(gè)組件A要求調(diào)用組件B(或它的類的一個(gè)實(shí)例),可以從容器中請(qǐng)求調(diào)用組件B,而不是創(chuàng)建組件B的一個(gè)實(shí)例。
這種工作方式為我們提供了許多優(yōu)點(diǎn):
我們可以更換一個(gè)組件,從他們本身或者第三方輕松創(chuàng)建。
在組件發(fā)布之前,我們可以充分的控制對(duì)象的初始化,并對(duì)對(duì)象進(jìn)行各種設(shè)置。
我們可以使用統(tǒng)一的方式從組件得到一個(gè)結(jié)構(gòu)化的全局實(shí)例
服務(wù)可以通過(guò)以下幾種方式注入到容器:
<?php
//Create the Dependency Injector Container
$di = new Phalcon\DI();
//By its class name
$di->set("request", 'Phalcon\Http\Request');
//Using an anonymous function, the instance will lazy loaded
$di->set("request", function(){
return new Phalcon\Http\Request();
});
//Registering directly an instance
$di->set("request", new Phalcon\Http\Request());
//Using an array definition
$di->set("request", array(
"className" => 'Phalcon\Http\Request'
));
在上面的例子中,當(dāng)向框架請(qǐng)求訪問(wèn)一個(gè)請(qǐng)求數(shù)據(jù)時(shí),它將首先確定容器中是否存在這個(gè)”reqeust”名稱的服務(wù)。
容器會(huì)反回一個(gè)請(qǐng)求數(shù)據(jù)的實(shí)例,開(kāi)發(fā)人員最終得到他們想要的組件。
在上面示例中的每一種方法都有優(yōu)缺點(diǎn),具體使用哪一種,由開(kāi)發(fā)過(guò)程中的特定場(chǎng)景來(lái)決定的。
用一個(gè)字符串來(lái)設(shè)定一個(gè)服務(wù)非常簡(jiǎn)單,但缺少靈活性。設(shè)置服務(wù)時(shí),使用數(shù)組則提供了更多的靈活性,而且可以使用較復(fù)雜的代碼。lambda函數(shù)是兩者之間一個(gè)很好的平衡,但也可能導(dǎo)致更多的維護(hù)管理成本。
Phalcon\DI 提供服務(wù)的延遲加載。除非開(kāi)發(fā)人員在注入服務(wù)的時(shí)候直接實(shí)例化一個(gè)對(duì)象,然后存存儲(chǔ)到容器中。在容器中,通過(guò)數(shù)組,字符串等方式存儲(chǔ)的服務(wù)都將被延遲加載,即只有在請(qǐng)求對(duì)象的時(shí)候才被初始化。
<?php
//Register a service "db" with a class name and its parameters
$di->set("db", array(
"className" => "Phalcon\Db\Adapter\Pdo\Mysql",
"parameters" => array(
"parameter" => array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog"
)
)
));
//Using an anonymous function
$di->set("db", function(){
return new Phalcon\Db\Adapter\Pdo\Mysql(array(
"host" => "localhost",
"username" => "root",
"password" => "secret",
"dbname" => "blog"
));
});
以上這兩種服務(wù)的注冊(cè)方式產(chǎn)生相同的結(jié)果。然后,通過(guò)數(shù)組定義的,在后面需要的時(shí)候,你可以修改服務(wù)參數(shù):
<?php
$di->setParameter("db", 0, array(
"host" => "localhost",
"username" => "root",
"password" => "secret"
));
從容器中獲得服務(wù)的最簡(jiǎn)單方式就是使用”get”方法,它將從容器中返回一個(gè)新的實(shí)例:
<?php $request = $di->get("request");
或者通過(guò)下面這種魔術(shù)方法的形式調(diào)用:
<?php
$request = $di->getRequest();
//Phalcon\DI 同時(shí)允許服務(wù)重用,為了得到一個(gè)已經(jīng)實(shí)例化過(guò)的服務(wù),可以使用 getShared() 方法的形式來(lái)獲得服務(wù)。
具體的 Phalcon\Http\Request 請(qǐng)求示例:
<?php
$request = $di->getShared("request");
參數(shù)還可以在請(qǐng)求的時(shí)候通過(guò)將一個(gè)數(shù)組參數(shù)傳遞給構(gòu)造函數(shù)的方式:
<?php
$component = $di->get("MyComponent", array("some-parameter", "other"));
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: