这篇文章是关于一般依赖关系注入和在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
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。