本文翻译自
Symfony
做者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Dependency Injection Container in PHP》 系列文章。php
依赖注入
设计模式很是简单,但又很难解释清楚。形成这个现象的主要缘由是,别的介绍 依赖注入
的文章里太多废话,让人混淆。下面我将经过一些更适合 PHP 的例子来说解它。html
HTTP 协议是无状态的,咱们的 Web 应用程序若是须要在请求之间存储用户信息,能够经过 COOKIE
或 SESSION
:laravel
$_SESSION['language'] = 'fr';
上述代码中,咱们将 language
存储在全局变量 $_SESSION
中,所以能够这样获取它:git
$user_language = $_SESSION['language'];
只有在 OOP
开发时中才会遇到 依赖注入
,所以假设咱们有一个封装 SESSION
的 SessionStorage
类:github
class SessionStorage { function __construct($cookieName='PHPSESSID') { session_name($cookieName); session_start(); } function set($key, $value) { $_SESSION[$key] = $value; } function get($key) { return $_SESSION[$key]; } // ... }
以及一个更高层的 User
类:设计模式
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 = new User(); $user->setLanguage('fr'); $user_language = $user->getLanguage();
这种方式看起来很完美,可是并不够灵活。好比:如今想修改会话的 COOKIE
名称(默认为 PHPSESSID
) ,怎么办?这时有一大堆办法:缓存
COOKIE
名称写死在 User
类中 SessionStorage
的构造函数中 (Hardcode):class User { function __construct() { $this->storage = new SessionStorage('SESSION_ID'); } // ... }
User
类外面定义一个常量:class User { function __construct() { $this->storage = new SessionStorage(SESSION_COOKIE_NAME); } // ... } define('SESSION_COOKIE_NAME', 'SESSION_ID');
COOKIE
名称做为 User
类构造函数的一个参数传进去:class User { function __construct($cookieName) { $this->storage = new SessionStorage($cookieName); } // ... } $user = new User('SESSION_ID');
SessionStorage
类加个选项数组:class User { function __construct($storageOptions) { $this->storage = new SessionStorage($storageOptions['cookie_name']); } // ... } $user = new User(['cookie_name' => 'SESSION_ID']);
上述方法都很糟糕:cookie
COOKIE
名称写死的话,每次想再更名,都要修改 User
类User
类的变化将取决于设置的常量User
自己无关的东西掺杂在了构造函数中SessionStorage
实例"注入"进 User
实例内部,而不是在 User
实例内部建立 SessionStorage
实例,就是 依赖注入
。class User { function __construct($storage) { $this->storage = $storage; } // ... }
很清爽吧!只需先建立 SessionStorage
实例,再建立 User
实例:session
$storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
用这个方法,配置 SessionStorage
很简单,给 User
替换 $storage
类型也很简单,都不须要去修改 User
类。这就实现了解耦。
依赖注入
并不限于构造函数:
class User { function __construct($storage) { $this->storage = $storage; } // ... }
class User { function setSessionStorage($storage) { $this->storage = $storage; } // ... }
class User { public $sessionStorage; } $user->sessionStorage = $storage;
做为经验, Constructor 注入
最适合必须的依赖关系,好比示例中的状况; Setter 注入
最适合可选依赖关系,好比缓存一个对象实例。
如今,大多数现代 PHP 框架都大量使用依赖注入来提供一组 去耦
但 粘合
的组件:
// symfony: A constructor injection example $dispatcher = new sfEventDispatcher(); $storage = new sfMySQLSessionStorage([ 'database' => 'session', 'db_table' => 'session', ]); $user = new sfUser($dispatcher, $storage, ['default_culture' => 'en']); // Zend Framework: A setter injection example $transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', [ 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, ]); $mailer = new Zend_Mail(); $mailer->setDefaultTransport($transport);
若是你有兴趣了解更多有关 依赖注入
的信息,我强烈建议你阅读 Martin Fowler 的介绍 或 Jeff Moore 的 PPT。你还能够看看我去年关于 依赖注入
的 PPT,其中我详细了介绍本文中所讨论的示例。
但愿本文能让你更好的理解 依赖注入
,在本系列的下一部分中,我将讨论 依赖注入容器
(Dependency Injection Containers)。
原创。 全部 Laravel 文章均已收录至 laravel-tips 项目。