依賴註冊的簡單接觸

##1.在組件內部建立依賴對象php

首先,咱們假設,咱們要開發一個組件命名爲SomeComponent。sql

這個組件中依賴一個數據庫鏈接。數據庫

組件依賴數據庫鏈接對象,咱們在組件中建立數據庫鏈接對象數組

###demo1:session

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();

在這個例子中,數據庫鏈接在component中被建立,這種方法是不切實際的,這樣作的話,咱們將不能改變數據庫鏈接參數及數據庫類型等一些參數。並且耦合性過高了。框架

#2.改進: 把依賴對象注入組件中ide

爲了解決上面所說的問題,咱們須要在使用前建立一個外部鏈接,並注入到容器中。就目前而言,這看起來是一個很好的解決方案:函數

在外部建立對象注入到依賴的類中,依賴的類被叫作容器測試

###demo2ui

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();

在容器中須要set的方法

#3.使用全局註冊的方式注入對象

如今咱們來考慮一個問題,咱們在應用程序中的不一樣地方使用此組件,將屢次建立數據庫鏈接。使用一種相似全局註冊表的方式,從這得到一個數據庫鏈接實例,而不是使用一次就建立一次。

###demo3

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();

##3.1全局註冊的進一步(對象的建立方式)

如今,讓咱們來想像一下,咱們必須在組件中實現兩個方法,首先須要建立一個新的數據庫鏈接,第二個老是得到一個共享鏈接:

demo3:

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());

到此爲止,咱們已經看到了如何使用依賴注入解決咱們的問題。不是在代碼內部建立依賴關係,而是讓其做爲一個參數傳遞,這使得咱們的程序更容易維護,下降程序代碼的耦合度,實現一種鬆耦合。可是從長遠來看,這種形式的依賴注入也有一些缺點。

#4 簡單依賴註冊的缺點

##4.1 建立依賴

例如,若是組件中有較多的依賴關係,咱們須要建立多個setter方法傳遞,或建立構造函數進行傳遞。另外,每次使用組件時,都須要建立依賴組件,使代碼維護不太易,咱們編寫的代碼可能像這樣

使用setter方法 或者使用構造方法注入對象

//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);

##4.2 移除依賴組件

咱們不得不在應用程序的許多地方建立這個對象。若是你不須要依賴的組件後,咱們又要去代碼注入部分移除構造函數中的參數或者是setter方法。爲了解決這個問題,咱們再次返回去使用一個全局註冊表來建立組件。可是,在建立對象以前,它增長了一個新的抽象層:

#5.使用全局註冊表建立對象

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);
  }

這一刻,咱們好像回到了問題的開始,咱們正在建立組件內部的依賴,咱們每次都在修改以及找尋一種解決問題的辦法,但這都不是很好的作法。

一種實用和優雅的來解決這些問題,是使用容器的依賴注入,像咱們在前面看到的,容器做爲全局註冊表,使用容器的依賴注入作爲一種橋樑來解決依賴可使咱們的代碼耦合度更低,很好的下降了組件的複雜性:

注入容器而不是注入對象,對象在容器中建立

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 在容器中注入BD服務
$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();

$di是一個容器對象

set 和get是容器的的方法

至關於容器時註冊樹,全局的樹,樹上有不少的對象掛在上面

如今,該組件只有訪問某種service的時候才須要它,若是它不須要,它甚至不初始化,以節約資源。該組件是高度解耦。他們的行爲,或者說他們的任何其餘方面都不會影響到組件自己。咱們的實現辦法


Phalcon\DI 是一個實現了服務的依賴注入功能的組件,它自己也是一個容器。 因爲Phalcon高度解耦,Phalcon\DI 是框架用來集成其餘組件的必不可少的部分,開發人員也可使用這個組件依賴注入和管理應用程序中不一樣類文件的實例。

本身理解:DI就是一個全局註冊樹

基本上,這個組件實現了 Inversion of Control 模式。基於此,對象再也不以構造函數接收參數或者使用setter的方式來實現注入,而是直接請求服務的依賴注入。這就大大下降了總體程序的複雜性,由於只有一個方法用以得到所須要的一個組件的依賴關係。 此外,這種模式加強了代碼的可測試性,從而使它不容易出錯。

在容器中註冊服務 框架自己或開發人員均可以註冊服務。當一個組件A要求調用組件B(或它的類的一個實例),能夠從容器中請求調用組件B,而不是建立組件B的一個實例。


咱們須要去容器中註冊服務,而後組件中使用服務 這種工做方式爲咱們提供了許多優勢:

咱們能夠更換一個組件,從他們自己或者第三方輕鬆建立。

在組件發佈以前,咱們能夠充分的控制對象的初始化,並對對象進行各類設置。 咱們可使用統一的方式從組件獲得一個結構化的全局實例

#6. 服務注入到容器的方式:

服務能夠經過如下幾種方式注入到容器

//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'
));

在上面的例子中,當向框架請求訪問一個請求數據時,它將首先肯定容器中是否存在這個」reqeust」名稱的服務。

容器會反回一個請求數據的實例,開發人員最終獲得他們想要的組件。

在上面示例中的每一種方法都有優缺點,具體使用哪種,由開發過程當中的特定場景來決定的。


  1. 用一個字符串來設定一個服務很是簡單,但缺乏靈活性 。
  1. 設置服務時,使用數組則提供了更多的靈活性,並且可使用較複雜的代碼。
  2. lambda函數是二者之間一個很好的平衡,但也可能致使更多的維護管理成本。

#7. Phalcon\DI Phalcon\DI 提供服務的延遲加載。除非開發人員在注入服務的時候直接實例化一個對象,而後存存儲到容器中。在容器中,經過數組,字符串等方式存儲的服務都將被延遲加載,即只有在請求對象的時候才被初始化

//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"
  ));
});

以上這兩種服務的註冊方式產生相同的結果。而後,經過數組定義的,在後面須要的時候,你能夠修改服務參數:

$di->setParameter("db", 0, array(
  "host" => "localhost",
  "username" => "root",
  "password" => "secret"
));

從容器中得到服務的最簡單方式就是使用」get」方法,它將從容器中返回一個新的實例:

$request = $di->get("request");

或者經過下面這種魔術方法的形式調用:

$request = $di->getRequest();

##7.1 服務的複用 Phalcon\DI 同時容許服務重用,爲了獲得一個已經實例化過的服務,可使用 getShared() 方法的形式來得到服務。

具體的 Phalcon\Http\Request 請求示例:

$request = $di->getShared("request");

參數還能夠在請求的時候經過將一個數組參數傳遞給構造函數的方式:

$component = $di->get("MyComponent", array("some-parameter", "other"));
```
相關文章
相關標籤/搜索