依赖注册的简单接触

##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"));
```
相关文章
相关标签/搜索