首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >什么是依赖注入

什么是依赖注入

作者头像
柳公子
发布2018-09-17 16:29:37
2.5K0
发布2018-09-17 16:29:37
举报
文章被收录于专栏:PhpZendoPhpZendo

本文是依赖注入(Depeendency Injection)系列教程的第一篇文章,本系列教程主要讲解如何使用 PHP 实现一个轻量级服务容器,教程包括:

本文是依赖注入(Depeendency Injection)系列教程的第一篇文章,本系列教程主要讲解如何使用 PHP 实现一个轻量级服务容器。


术语

  • Depeendency Injection 译作 依赖注入
  • Depeendency Injection Container 译作 依赖注入容器
  • Container 译作 容器
  • Service Container 译作 服务容器
  • Session 译作 会话
  • Object-Oriented 译作 面向对象
  • mock 译作 模拟
  • anti-patterns 译作 反模式
  • hardcoded 译作 硬编码

这篇文章不会涉及有关「容器」相关知识的讲解,而是通过一些实际的案例带你去了解「依赖注入」这种设计模式试图解决哪些问题,以及如何帮助我们解决这些问题的。如果您已经掌握「依赖注入」相关概念,那么可以跳过这篇文章。

「依赖注入」也许是我所知的最简单的设计模式之一,有可能您已经在项目中使用过「依赖注入」,但同时它也是最难以讲透彻的模式之一。究其原因,大概是因为市面上已有讲解「依赖注入」模式的文章,大多都在使用一些毫无实际意义的示例。在此之前,我已经尝试使用 PHP 语言来设计一些「依赖注入」的示例。由于 PHP 是一门 Web 开发而生,我们还是以一些简单的 Web 实例作为开场较为合适。

由于 HTTP 协议是无状态的协议,所以 Web 应用需要一种技术能够存储用户信息。通过使用 Cookie 或者 PHP 内置的「会话」机制能够轻松实现这样的需求:

<?php
$_SESSION = 'fr';

上例可以将用户选择的语言存储到会话的 language 变量里。之后,这位用户发起的请求,都可以从 $_SESSION 数组中获取 language 的值:

<?php
$user_language = $_SESSION['language'];

由于「依赖注入」基于面向对象设计,所以我们需要将上面的功能封装到 SessionStorage 类里:

<?php
class SessionStorage
{
    public function __construct($cookieName = 'PHP_SESS_ID')
    {
        session_name($cookieName);
        session_start();
    }

    public function set($key, $value)
    {
        $_SESSION[$key] = $value;
    }

    public function get($key)
    {
        return $_SESSION[$key];
    }
}

以及一个提供接口服务的类 User:

<?php
class User
{
    protected $storage;

    public function __construct()
    {
        $this->storage = new SessionStorage();
    }

    public function setLanguage($language)
    {
        $this->storage->set('language', $language);
    }

    public function getLanguage()
    {
        return $this->storage->get('language');
    }
}

这个实例非常简单,并且 User 类调用方法也十分简单:

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

一切都如此完美... 直到你需要扩展它。比如,你该如何修改 $this->storage 实例中的 cookie 名称?一般有如下解决方案:

  • 直接在 User 类里面创建 SessionStorage 实例时的 cookie 名称硬编码到它的构造函数:
<?php
class User
{
    public function __construct()
    {
        $this->storage = new SessionStorage('SESSION_ID');
    }
}
  • 通过在 User 类之外定义一个常量:
<?php
class User
{
    public function __construct()
    {
        $this->storage = new SessionStorage(STORAGE_SESSION_NAME);
    }
}

define('STORAGE_SESSION_NAME', 'SESSION_ID');
  • 将 User 类的构造函数重构,以接受一个会话名称:
<?php
class User
{
  function __construct($sessionName)
  {
    $this->storage = new SessionStorage($sessionName);
  }

  // ...
}

$user = new User('SESSION_ID');
  • 或者 User 构造函数接收一个 Storage 类的选项配置:
<?php    
class User
{
    public function __construct($storageOptions)
    {
        $this->storage = new SessionStorage($storageOptions['session_name']);
    }

    // ...
}

$user = new User(array('session_name' => 'SESSION_ID'));

所有这些方案都不太好。在 User 类里面硬编码并没有解决实际问题,后续你依旧无法在不修改 User 类代码的情况下实现更改会话名称的目的。使用一个常量也是一个坏主意,因为 User 类现在依赖于这个常量来设置。将会话名称作为参数传递或者作为一组选项可能是最好的解决方案,但是仍然很糟糕,因为这种方式将与 User 类无关的数据与 User 类耦合在一起。

另外,还有个问题也没办法轻松的解决:如何修改 SessionStorage 类?比如,需要使用「模拟」对象替换它用于测试。或者,需要替换会话存储引擎到数据库表或者内存。目前来看,我们无法在不修改 User 类的情况下轻松实现。

「依赖注入」就是解决这种的问题,通过将 SessionStorage 对象以构造函数的参数传给 User 实例,替换直接在 User 类中实例化的方式即可实现以上需求:

<?php
class User
{
    public function __construct($storage)
    {
        $this->storage = $storage;
    }

    // ...
}

这就是「依赖注入」!此时,如果要使用 User 类,会稍微比之前要复杂一些:

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

这样配置会话存储对象和替换会话存储实现类都可以轻松完成。得益于依赖的分离设计,在不改变 User 类的情况下,一切皆有可能。

Pico Container website 是这样描述依赖注入的:

「依赖注入」通过以构造函数参数,设值方法或属性字段等方式将具体组件传递给依赖方(译注:使用者)。 与其他设计模式一样,依赖注入也有一些反模式。Pico Container website 描述了其中的一些反模式。

「依赖注入」并不局限于通过构造函数注入这一种注入形式:

  • 以构造函数注入:
<?php
 class User
{
    public function __construct($storage)
    {
        $this->storage = $storage;
    }

    // ...
}
  • 以设值方法注入
<?php
class User
{
    public function setSessionStorage($storage)
    {
        $this->storage = $storage;
    }
}
  • 以类成员变量方式注入:
<?php
class User
{
    public $sessionStorage;
}

$user->sessionStorage = $storage;

从我的经验来看,通过构造函数注入适用于必要的依赖,如上例;设值注入适用于可选的依赖,如项目需要一个缓存功能的实现。

如今,很多 PHP 现代框架都依赖于「依赖注入」设计模式已达到高内聚低耦合的目标:

<?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 Moore 的分享。此外还有我去年有关 依赖注入的分享,这篇文章有更加细腻的依赖注入的解读(译注:但是很遗憾我一直打不开这个连接 ? )。

以上,就是今天全部内容。希望您对「依赖注入」有了更加深入的了解。下一篇文章将聊聊「依赖注入容器」

资料

https://www.martinfowler.com/articles/injection.html

Dependency Injection: Service Locator, Factory, Constructor Injection, DIC

https://laravel-china.org/articles/6139/laravel-container-container-concept-detailed-last 原文 : http://fabien.potencier.org/what-is-dependency-injection.html

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年5月1日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 术语
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档