什么是依赖注入?
admin
2023-07-03 10:24:03
0次

这篇文章是关于一般依赖关系注入和在PHP中实现依赖注入容器系列的第一部分。

今天我不会谈论容器然而我想以一些具体的示例介绍依赖注入的概念希望说明尝试去解决问题和它给开发者带来的好处。如果你已经知道依赖注入的概念你可以略过本文等待下一篇。

依赖注入或许是我所知道的最简单的设计模式之一。或许你已经使用过依赖注入。但它是解释最困难的一个。我认为部分原因在于大多数介绍依赖注入的时候使用了没有意义的示例。我试图想出更符合PHP世界的例子由于PHP这种语言主要是用来做WEB开发的让我们举一个简单的WEB例子。

为了克服HTTP协议的无状态性web应用程序需要一种方法来存储web请求之间的用户信息。当然通过使用一个cookie或者甚至更好的方式通过使用PHP内置的会话机制(session)实现是非常简单的。

$_SESSION['language'] = 'fr';

上面的代码中用语言会话变量存储用户的语言。因此针对同一个用户的后续请求在超全局变量$_SESSION数组中的语言是有效的。

$user_language = $_SESSION['language'];

在面向对象的世界中依赖关系注入才有意义让我们假设我们有一个SessionStorage 类封装了PHP的会话机制

class SessionStorage
{
    function __construct($cookieName = 'PHP_SESS_ID')
    {
        session_name($cookieName);
        session_start();
    }
    function set($key, $value)
    {
        $_SESSION[$key] = $value;
    }
    function get($key)
    {
        return $_SESSION[$key];
    }
    // ...
}

... 和一个提供了更高级别接口的用户类

class User
{
    protected $storage;
    function __construct()
    {
        $this->storage = new SessionStorage();
    }
    function setLanguage($language)
    {
        $this->storage->set('language', $language);
    }
    function getLanguage()
    {
        return $this->storage->get('language');
    }
    // ...
}

这些类是足够简单的使用User类相比更容易

$user = new User();
$user->setLanguage('fr');
$user_language = $user->getLanguage();

全部都很好...直到你想要更多的灵活性。如果你想要修改实例会话cookie的名称下面是一些随机的可能性

·在User类里面是硬编码的名称在他的SessionStorage 构造函数中:

class User
{
    function __construct()
    {
        $this->storage = new SessionStorage('SESSION_ID');
    }
    // ...
}

在User类的外面定义一个常量

class User
{
  function __construct()
  {
    $this->storage = new SessionStorage(STORAGE_SESSION_NAME);
  }
  // ...
}
define('STORAGE_SESSION_NAME', 'SESSION_ID');

添加会话的名称session name作为User类的构造函数的参数

class User
{
    function __construct($sessionName)
    {
        $this->storage = new SessionStorage($sessionName);
    }
                                                                                                                         
    // ...
}
$user = new User('SESSION_ID');

为存储类SessionStorage添加一个选项数组

class User
{
    function __construct($storageOptions)
    {
        $this->storage = new SessionStorage($storageOptions['session_name']);
}
    // ...
}
$user = new User(array('session_name' => 'SESSION_ID'));

所有的这些选择都是相当糟糕的。在User类中的硬编码会话名称session name并没有真正解决问题你不能轻易改变想法以后而无需再次更改User类。使用一个常量之所以糟糕的因为User类现在依赖一个常量被设置。传递会话名称session name作为一个参数或者作为一个选项数组是或许是最好的解决方法但它仍然很糟糕。用不相关的参数给对象本身打乱了User类构造函数的参数。

但是仍然有问题不能被解决我怎么才能更改SessionStorage 类呢例如为了方便测试将它替换为一个假对象。或者也许是因为你想要在数据库表中或者内存中存储会话sessions。当前实现是不可能的除非你修改User类。加入依赖注入而不是在User类里面建立SessionStorage 对象让我们在User对象通过传递一个构造参数注入SessionStorage对象

class User
{
    function __construct($storage)
    {
        $this->storage = $storage;
    }
    // ...
}

这就是依赖注入。仅此而已使用User类现在多了一点包括你首先需要建立SessionStorage 对象

$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);

现在配置会话存储对象session storage object是非常简单的和替换会话存储类也是非常容易。并且由于关注点的分离User类没有改变一切都是可能的。

Pico Container website介绍的依赖注入就像这样的

"Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields."

“依赖注入是其中的组件通过他们的构造器、方法、或者直接到字段的方式”给与他们的依赖关系。

至于其他的设计模式依赖注入也有一些反模式。Pico Container website介绍了其中的一些。

依赖注入不局限于构造函数注入:

·构造器注入:

class User
{
    function __construct($storage)
    {
        $this->storage = $storage;
    }
    // ...
}


·
Setter注入:

class User
{
    function setSessionStorage($storage)
    {
        $this->storage = $storage;
    }
    // ...
}


·
属性注入:

class User
{
    public $sessionStorage;
}
$user->sessionStorage = $storage;


作为经验法则构造器所需要的依赖关系是最好的就像在我们的例子中
setter注入最适合可选的依赖关系比如像一个缓存实例对象。如今最现代化的PHP框架大量使用了依赖注入提供了一组解耦但内聚的组件。

// symfony: A constructor injection example
$dispatcher = new sfEventDispatcher();
$storage = new sfMySQLSessionStorage(array('database' => 'session', 'db_table' => 'session'));
$user = new sfUser($dispatcher, $storage, array('default_culture' => 'en'));
// Zend Framework: A setter injection example
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
    'auth'=> 'login',
    'username' => 'foo',
    'password' => 'bar',
    'ssl'=> 'ssl',
    'port'=> 465,
));
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);


如果你有兴趣学习更多关于依赖注入我强烈建议你阅读
Martin Fowler introduction或者优秀的Jeff More presentation。你还可以去看看我去年给依赖注入做的介绍presentation在这篇文章中谈论的在里面讲了更多在例子上的细节。

今天就到这了。我希望你现在对依赖注入的概念有一个更好的理解。在本系列的下一篇文章我会谈谈依赖注入的容器。


   原文地址http://fabien.potencier.org/article/11/what-is-dependency-injection



   

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...