Laravel框架的依賴註入確實很強大,並且通過容器實現依賴註入可以有選擇性的載入需要的服務,提高初始化框架的開銷,下麵是我在網上看到的一個帖子,寫的很好拿來與大家分享,文章從開始按照傳統的類設計資料庫連接一直到通過容器載入服務這個高度解耦的設計展示了依賴註入的強大之處,值得我們借鑒和學習。--.....
Laravel框架的依賴註入確實很強大,並且通過容器實現依賴註入可以有選擇性的載入需要的服務,減少初始化框架的開銷,下麵是我在網上看到的一個帖子,寫的很好拿來與大家分享,文章從開始按照傳統的類設計資料庫連接一直到通過容器載入服務這個高度解耦的設計展示了依賴註入的強大之處,值得我們借鑒和學習。
-----------------------------------------------------------分割線下麵是大牛的原文----------------------------------------------------------
原文連接(http://www.yuansir-web.com/2014/03/20)
首先,我們假設,我們要開發一個組件命名為SomeComponent。這個組件中現在將要註入一個資料庫連接。在這個例子中,資料庫連接在component中被創建,這種方法是不切實際的,這樣做的話,我們將不能改變資料庫連接參數及資料庫類型等一些參數。
1 <?php 2 3 class SomeComponent 4 { 5 6 /** 7 * The instantiation of the connection is hardcoded inside 8 * the component so is difficult to replace it externally 9 * or change its behavior 10 */ 11 public function someDbTask() 12 { 13 $connection = new Connection(array( 14 "host" => "localhost", 15 "username" => "root", 16 "password" => "secret", 17 "dbname" => "invo" 18 )); 19 20 // ... 21 } 22 23 } 24 25 $some = new SomeComponent(); 26 $some->someDbTask();
為瞭解決上面所說的問題,我們需要在使用前創建一個外部連接,並註入到容器中。就目前而言,這看起來是一個很好的解決方案:
1 <?php 2 3 class SomeComponent 4 { 5 6 protected $_connection; 7 8 /** 9 * Sets the connection externally 10 */ 11 public function setConnection($connection) 12 { 13 $this->_connection = $connection; 14 } 15 16 public function someDbTask() 17 { 18 $connection = $this->_connection; 19 20 // ... 21 } 22 23 } 24 25 $some = new SomeComponent(); 26 27 //Create the connection 28 $connection = new Connection(array( 29 "host" => "localhost", 30 "username" => "root", 31 "password" => "secret", 32 "dbname" => "invo" 33 )); 34 35 //Inject the connection in the component 36 $some->setConnection($connection); 37 38 $some->someDbTask();
現在我們來考慮一個問題,我們在應用程式中的不同地方使用此組件,將多次創建資料庫連接。使用一種類似全局註冊表的方式,從這獲得一個資料庫連接實例,而不是使用一次就創建一次。
1 <?php 2 3 class Registry 4 { 5 6 /** 7 * Returns the connection 8 */ 9 public static function getConnection() 10 { 11 return new Connection(array( 12 "host" => "localhost", 13 "username" => "root", 14 "password" => "secret", 15 "dbname" => "invo" 16 )); 17 } 18 19 } 20 21 class SomeComponent 22 { 23 24 protected $_connection; 25 26 /** 27 * Sets the connection externally 28 */ 29 public function setConnection($connection){ 30 $this->_connection = $connection; 31 } 32 33 public function someDbTask() 34 { 35 $connection = $this->_connection; 36 37 // ... 38 } 39 40 } 41 42 $some = new SomeComponent(); 43 44 //Pass the connection defined in the registry 45 $some->setConnection(Registry::getConnection()); 46 47 $some->someDbTask();
現在,讓我們來想像一下,我們必須在組件中實現兩個方法,首先需要創建一個新的資料庫連接,第二個總是獲得一個共用連接:
1 <?php 2 3 class Registry 4 { 5 6 protected static $_connection; 7 8 /** 9 * Creates a connection 10 */ 11 protected static function _createConnection() 12 { 13 return new Connection(array( 14 "host" => "localhost", 15 "username" => "root", 16 "password" => "secret", 17 "dbname" => "invo" 18 )); 19 } 20 21 /** 22 * Creates a connection only once and returns it 23 */ 24 public static function getSharedConnection() 25 { 26 if (self::$_connection===null){ 27 $connection = self::_createConnection(); 28 self::$_connection = $connection; 29 } 30 return self::$_connection; 31 } 32 33 /** 34 * Always returns a new connection 35 */ 36 public static function getNewConnection() 37 { 38 return self::_createConnection(); 39 } 40 41 } 42 43 class SomeComponent 44 { 45 46 protected $_connection; 47 48 /** 49 * Sets the connection externally 50 */ 51 public function setConnection($connection){ 52 $this->_connection = $connection; 53 } 54 55 /** 56 * This method always needs the shared connection 57 */ 58 public function someDbTask() 59 { 60 $connection = $this->_connection; 61 62 // ... 63 } 64 65 /** 66 * This method always needs a new connection 67 */ 68 public function someOtherDbTask($connection) 69 { 70 71 } 72 73 } 74 75 $some = new SomeComponent(); 76 77 //This injects the shared connection 78 $some->setConnection(Registry::getSharedConnection()); 79 80 $some->someDbTask(); 81 82 //Here, we always pass a new connection as parameter 83 $some->someOtherDbTask(Registry::getConnection());
到此為止,我們已經看到瞭如何使用依賴註入解決我們的問題。不是在代碼內部創建依賴關係,而是讓其作為一個參數傳遞,這使得我們的程式更容易維護,降低程式代碼的耦合度,實現一種松耦合。但是從長遠來看,這種形式的依賴註入也有一些缺點。
例如,如果組件中有較多的依賴關係,我們需要創建多個setter方法傳遞,或創建構造函數進行傳遞。另外,每次使用組件時,都需要創建依賴組件,使代碼維護不太易,我們編寫的代碼可能像這樣:
1 <?php 2 3 //Create the dependencies or retrieve them from the registry 4 $connection = new Connection(); 5 $session = new Session(); 6 $fileSystem = new FileSystem(); 7 $filter = new Filter(); 8 $selector = new Selector(); 9 10 //Pass them as constructor parameters 11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector); 12 13 // ... or using setters 14 15 $some->setConnection($connection); 16 $some->setSession($session); 17 $some->setFileSystem($fileSystem); 18 $some->setFilter($filter); 19 $some->setSelector($selector);
我想,我們不得不在應用程式的許多地方創建這個對象。如果你不需要依賴的組件後,我們又要去代碼註入部分移除構造函數中的參數或者是setter方法。為瞭解決這個問題,我們再次返回去使用一個全局註冊表來創建組件。但是,在創建對象之前,它增加了一個新的抽象層:
1 <?php 2 3 class SomeComponent 4 { 5 6 // ... 7 8 /** 9 * Define a factory method to create SomeComponent instances injecting its dependencies 10 */ 11 public static function factory() 12 { 13 14 $connection = new Connection(); 15 $session = new Session(); 16 $fileSystem = new FileSystem(); 17 $filter = new Filter(); 18 $selector = new Selector(); 19 20 return new self($connection, $session, $fileSystem, $filter, $selector); 21 } 22 23 }
這一刻,我們好像回到了問題的開始,我們正在創建組件內部的依賴,我們每次都在修改以及找尋一種解決問題的辦法,但這都不是很好的做法。
一種實用和優雅的來解決這些問題,是使用容器的依賴註入,像我們在前面看到的,容器作為全局註冊表,使用容器的依賴註入做為一種橋梁來解決依賴可以使我們的代碼耦合度更低,很好的降低了組件的複雜性:
1 <?php 2 3 class SomeComponent 4 { 5 6 protected $_di; 7 8 public function __construct($di) 9 { 10 $this->_di = $di; 11 } 12 13 public function someDbTask() 14 { 15 16 // Get the connection service 17 // Always returns a new connection 18 $connection = $this->_di->get('db'); 19 20 } 21 22 public function someOtherDbTask() 23 { 24 25 // Get a shared connection service, 26 // this will return the same connection everytime 27 $connection = $this->_di->getShared('db'); 28 29 //This method also requires a input filtering service 30 $filter = $this->_db->get('filter'); 31 32 } 33 34 } 35 36 $di = new Phalcon\DI(); 37 38 //Register a "db" service in the container 39 $di->set('db', function(){ 40 return new Connection(array( 41 "host" => "localhost", 42 "username" => "root", 43 "password" => "secret", 44 "dbname" => "invo" 45 )); 46 }); 47 48 //Register a "filter" service in the container 49 $di->set('filter', function(){ 50 return new Filter(); 51 }); 52 53 //Register a "session" service in the container 54 $di->set('session', function(){ 55 return new Session(); 56 }); 57 58 //Pass the service container as unique parameter 59 $some = new SomeComponent($di); 60 61 $some->someTask();
現在,該組件只有訪問某種service的時候才需要它,如果它不需要,它甚至不初始化,以節約資源。該組件是高度解耦。他們的行為,或者說他們的任何其他方面都不會影響到組件本身。
我們的實現辦法¶
Phalcon\DI 是一個實現了服務的依賴註入功能的組件,它本身也是一個容器。
由於Phalcon高度解耦,Phalcon\DI 是框架用來集成其他組件的必不可少的部分,開發人員也可以使用這個組件依賴註入和管理應用程式中不同類文件的實例。
基本上,這個組件實現了 Inversion of Control 模式。基於此,對象不再以構造函數接收參數或者使用setter的方式來實現註入,而是直接請求服務的依賴註入。這就大大降低了整體程式的複雜性,因為只有一個方法用以獲得所需要的一個組件的依賴關係。
此外,這種模式增強了代碼的可測試性,從而使它不容易出錯。
在容器中註冊服務¶
框架本身或開發人員都可以註冊服務。當一個組件A要求調用組件B(或它的類的一個實例),可以從容器中請求調用組件B,而不是創建組件B的一個實例。
這種工作方式為我們提供了許多優點:
我們可以更換一個組件,從他們本身或者第三方輕鬆創建。
在組件發佈之前,我們可以充分的控制對象的初始化,並對對象進行各種設置。
我們可以使用統一的方式從組件得到一個結構化的全局實例
服務可以通過以下幾種方式註入到容器:
1 <?php 2 3 //Create the Dependency Injector Container 4 $di = new Phalcon\DI(); 5 6 //By its class name 7 $di->set("request", 'Phalcon\Http\Request'); 8 9 //Using an anonymous function, the instance will lazy loaded 10 $di->set("request", function(){ 11 return new Phalcon\Http\Request(); 12 }); 13 14 //Registering directly an instance 15 $di->set("request", new Phalcon\Http\Request()); 16 17 //Using an array definition 18 $di->set("request", array( 19 "className" => 'Phalcon\Http\Request' 20 ));
在上面的例子中,當向框架請求訪問一個請求數據時,它將首先確定容器中是否存在這個”reqeust”名稱的服務。
容器會反回一個請求數據的實例,開發人員最終得到他們想要的組件。
在上面示例中的每一種方法都有優缺點,具體使用哪一種,由開發過程中的特定場景來決定的。
用一個字元串來設定一個服務非常簡單,但缺少靈活性。設置服務時,使用數組則提供了更多的靈活性,而且可以使用較複雜的代碼。lambda函數是兩者之間一個很好的平衡,但也可能導致更多的維護管理成本。
Phalcon\DI 提供服務的延遲載入。除非開發人員在註入服務的時候直接實例化一個對象,然後存存儲到容器中。在容器中,通過數組,字元串等方式存儲的服務都將被延遲載入,即只有在請求對象的時候才被初始化。
1 <?php 2 3 //Register a service "db" with a class name and its parameters 4 $di->set("db", array( 5 "className" => "Phalcon\Db\Adapter\Pdo\Mysql", 6 "parameters" => array( 7 "parameter" => array( 8 "host" => "localhost", 9 "username" => "root", 10 "password" => "secret", 11 "dbname" => "blog" 12 ) 13 ) 14 )); 15 16 //Using an anonymous function 17 $di->set("db", function(){ 18 return new Phalcon\Db\Adapter\Pdo\Mysql(array( 19 "host" => "localhost", 20 "username" => "root", 21 "password" => "secret", 22 "dbname" => "blog" 23 )); 24 });
以上這兩種服務的註冊方式產生相同的結果。然後,通過數組定義的,在後面需要的時候,你可以修改服務參數:
1 <?php 2 3 $di->setParameter("db", 0, array( 4 "host" => "localhost", 5 "username" => "root", 6 "password" => "secret" 7 ));
從容器中獲得服務的最簡單方式就是使用”get”方法,它將從容器中返回一個新的實例:
1 <?php 2 $request = $di->get("request");
或者通過下麵這種魔術方法的形式調用:
1 <?php 2 3 $request = $di->getRequest(); 4 5 Phalcon\DI 同時允許服務重用,為了得到一個已經實例化過的服務,可以使用 getShared() 方法的形式來獲得服務。
具體的 Phalcon\Http\Request 請求示例:
1 <?php 2 3 $request = $di->getShared("request");
參數還可以在請求的時候通過將一個數組參數傳遞給構造函數的方式:
1 <?php 2 3 $component = $di->get("MyComponent", array("some-parameter", "other"))