摘要:依赖注入并不限于构造函数作为经验,注入最适合必须的依赖关系,比如示例中的情况注入最适合可选依赖关系,比如缓存一个对象实例。
本文翻译自 Symfony 作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Dependency Injection Container in PHP》 系列文章。
Part 1: What is Dependency Injection?
Part 2: Do you need a Dependency Injection Container?
Part 3: Introduction to the Symfony Service Container
Part 4: Symfony Service Container: Using a Builder to create Services
Part 5: Symfony Service Container: Using XML or YAML to describe Services
Part 6: The Need for Speed
依赖注入 设计模式非常简单,但又很难解释清楚。造成这个现象的主要原因是,别的介绍 依赖注入 的文章里太多废话,让人混淆。下面我将通过一些更适合 PHP 的例子来讲解它。
HTTP 协议是无状态的,我们的 Web 应用程序如果需要在请求之间存储用户信息,可以通过 COOKIE 或 SESSION :
$_SESSION["language"] = "fr";
上述代码中,我们将 language 存储在全局变量 $_SESSION 中,因此可以这样获取它:
$user_language = $_SESSION["language"];
只有在 OOP 开发时中才会遇到 依赖注入 ,因此假设我们有一个封装 SESSION 的 SessionStorage 类:
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 名称写死的话,每次想再改名,都要修改 User 类
使用常量的话,User 类的变化将取决于设置的常量
使用参数或者选项数组看起来很灵活,但它把与 User 本身无关的东西掺杂在了构造函数中
通过构造函数,把一个外部的 SessionStorage 实例"注入"进 User 实例内部,而不是在 User 实例内部创建 SessionStorage 实例,就是 依赖注入。class User { function __construct($storage) { $this->storage = $storage; } // ... }
很清爽吧!只需先创建 SessionStorage 实例,再创建 User 实例:
$storage = new SessionStorage("SESSION_ID"); $user = new User($storage);
用这个方法,配置 SessionStorage 很简单,给 User 替换 $storage 类型也很简单,都不需要去修改 User 类。这就实现了解耦。
依赖注入 并不限于构造函数:
Constructor Injection:
class User { function __construct($storage) { $this->storage = $storage; } // ... }
Setter Injection:
class User { function setSessionStorage($storage) { $this->storage = $storage; } // ... }
Property Injection:
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 项目。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/25954.html
摘要:上文书,创建对象需要先创建对象。创建对象的杂活是嵌入在中的。对象使用来管理依赖关系非常好,但不是必须的。很容易实现,但手工维护各种乱七八糟的对象还是很麻烦。所有文章均已收录至项目。 本文翻译自 Symfony 作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Depend...
摘要:意味着依赖被注入进构造函数或者方法如果需要复用实例,可以定义为单例可以用接口或任何名称来代替具体类。技能重写构造函数参数方法允许将附加参数传递给构造函数。 本文大部分翻译自 DAVE JAMES MILLER 的 《Laravel’s Dependency Injection Container in Depth》 。 上文介绍了 Dependency Injection Contai...
摘要:引述最近看设计模式以及代码,对于控制反转以及依赖注入这些概念非常困惑,于是找了一些资料,以下是对于控制反转的一下理解。其中最常见的方式叫做依赖注入,简称,还有一种方式叫依赖查找。在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。 引述 最近看设计模式以及laravel代码,对于控制反转以及依赖注入这些概念非常困惑,于是找了一些资料,以下是对于控制反转的一下理解。 概念 Io...
摘要:划下重点,服务容器是用于管理类的依赖和执行依赖注入的工具。类的实例化及其依赖的注入,完全由服务容器自动的去完成。 本文首发于 深入剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬你们的赞同,谢谢。 之前在 深度挖掘 Laravel 生命周期 一文中,我们有去探究 Laravel 究竟是如何接收 HTTP 请求,又是如何生成响应并最终呈现给用户的工作原理。 本章将带领大...
摘要:依赖注入和控制反转,这两个词经常一起出现。一句话表述他们之间的关系依赖注入是控制反转的一种实现方式。而两者有大量的代码都是可以共享的,这就是依赖注入的使用场景了。下一步就是创建具体的依赖内容,然后注入到需要的地方这里的等于这个对象。 前言 React 是一个十分庞大的库,由于要同时考虑 ReactDom 和 ReactNative ,还有服务器渲染等,导致其代码抽象化程度很高,嵌套层级...
阅读 3420·2021-11-08 13:30
阅读 3538·2019-08-30 15:55
阅读 632·2019-08-29 15:16
阅读 1703·2019-08-26 13:57
阅读 2066·2019-08-26 12:18
阅读 755·2019-08-26 11:36
阅读 1700·2019-08-26 11:30
阅读 2938·2019-08-23 16:46