译文首发于 什么是依赖注入,转载请注明出处。
本文是依赖注入(Depeendency Injection)系列教程的第一篇文章,本系列教程主要讲解如何使用 PHP 实现一个轻量级服务容器,教程包括:php
这篇文章不会涉及有关「容器」相关知识的讲解,而是经过一些实际的案例带你去了解「依赖注入」这种设计模式试图解决哪些问题,以及如何帮助咱们解决这些问题的。若是您已经掌握「依赖注入」相关概念,那么能够跳过这篇文章。html
「依赖注入」也许是我所知的最简单的设计模式之一,有可能您已经在项目中使用过「依赖注入」,但同时它也是最难以讲透彻的模式之一。究其缘由,大概是由于市面上已有讲解「依赖注入」模式的文章,大多都在使用一些毫无实际意义的示例。在此以前,我已经尝试使用 PHP 语言来设计一些「依赖注入」的示例。因为 PHP 是一门 Web 开发而生,咱们仍是以一些简单的 Web 实例做为开场较为合适。laravel
因为 HTTP 协议是无状态的协议,因此 Web 应用须要一种技术可以存储用户信息。经过使用 Cookie 或者 PHP 内置的「会话」机制可以轻松实现这样的需求:web
<?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 名称?通常有以下解决方案:性能优化
<?php class User { public function __construct() { $this->storage = new SessionStorage('SESSION_ID'); } }
<?php class User { public function __construct() { $this->storage = new SessionStorage(STORAGE_SESSION_NAME); } } define('STORAGE_SESSION_NAME', 'SESSION_ID');
<?php class User { function __construct($sessionName) { $this->storage = new SessionStorage($sessionName); } // ... } $user = new User('SESSION_ID');
<?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 类耦合在一块儿。cookie
另外,还有个问题也没办法轻松的解决:如何修改 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/...
http://www.mendoweb.be/blog/d...
https://laravel-china.org/art...