Gri*_*lij 18 php design-patterns dependency-injection
DI背后的要点是减轻一个类创建和准备它依赖的对象并将它们推入.这听起来很合理,但有时一个类不需要所有的对象,它们被推入它来执行它的功能.这背后的原因是"早期返回"发生在无效的用户输入或之前所需对象之一抛出的异常,或者在代码块运行之前实例化对象所必需的某个值不可用.
更实际的例子:
因此,推动所有必要组件的方式与某些组件的创建和从未使用的方式相矛盾的是"延迟加载",这有点不实用且影响性能.就PHP而言 - 加载,解析和编译更多文件.如果被推入的对象具有自己的依赖关系,则这尤其痛苦.
我看到它有3种方法,其中2种听起来不太好:
问题是处理这种情况的最佳方法是什么/你们使用什么?
更新:@GordonM这里有3种方法的例子:
//inject factory example
interface IFactory{
function factory();
}
class Bartender{
protected $_factory;
public function __construct(IFactory $f){
$this->_factory = $f;
}
public function order($data){
//validating $data
//... return or throw exception
//validation passed, order must be saved
$db = $this->_factory->factory(); //! factory instance * num necessary components
$db->insert('orders', $data);
//...
}
}
/*
inject provider example
assuming that the provider prepares necessary objects
(i.e. injects their dependencies as well)
*/
interface IProvider{
function get($uid);
}
class Router{
protected $_provider;
public function __construct(IProvider $p){
$this->_provider = $p;
}
public function route($str){
//... match $str against routes to resolve class and method
$inst = $this->_provider->get($class);
//...
}
}
//inject callback (old fashion way)
class MyProvider{
protected $_db;
public function getDb(){
$this->_db = $this->_db ? $this->_db : new mysqli();
return $this->_db;
}
}
class Bartender{
protected $_db;
public function __construct(array $callback){
$this->_db = $callback;
}
public function order($data){
//validating $data
//... return or throw exception
//validation passed, order must be saved
$db = call_user_func_array($this->_db, array());
$db->insert('orders', $data);
//...
}
}
//the way it works under the hood:
$provider = new MyProvider();
$db = array($provider, 'getDb');
new Bartender($db);
//inject callback (the PHP 5.3 way)
class Bartender{
protected $_db;
public function __construct(Closure $callback){
$this->_db = $callback;
}
public function order($data){
//validating $data
//... return or throw exception
//validation passed, order must be saved
$db = call_user_func_array($this->_db, array());
$db->insert('orders', $data);
//...
}
}
//the way it works under the hood:
static $conn = null;
$db = function() use ($conn){
$conn = $conn ? $conn : new mysqli();
return $conn;
};
new Bartender($db);
Run Code Online (Sandbox Code Playgroud)
我最近在计划一个我想尽可能做的主要项目时一直在考虑这个问题(坚持LoD,没有硬编码依赖等).我的第一个想法是"注入工厂"的方法,但我不确定这是要走的路.谷歌的清洁代码谈话声称,如果你通过一个对象来获取你真正想要的对象,那么你就违反了LoD.这似乎排除了注入工厂的想法,因为你必须通过工厂来获得你真正想要的东西.也许我错过了一些让它没问题的观点,但直到我确定我正在考虑其他方法.
你怎么做功能注射?我想你会传入一个回调函数来实现你想要的对象的实例化,但代码示例会很好.
如果您可以使用您提到的三种样式的代码示例来更新您的问题,那么它可能会很有用.我特别渴望看到"注射注射器",即使它是反模式.
确实发生的一个想法是代理对象.它实现了与您想要传入的实际对象相同的接口,但它不是实现任何东西,而是只保存真实类的实例并将方法调用转发给它.
interface MyInterface
{
public function doFoo ();
public function isFoo ();
// etc
}
class RealClass implements MyInterface
{
public function doFoo ()
{
return ('Foo!');
}
public function isFoo ()
{
return ($this -> doFoo () == 'Foo!'? true: false);
}
// etc
}
class RealClassProxy implements MyInterface
{
private $instance = NULL;
/**
* Do lazy instantiation of the real class
*
* @return RealClass
*/
private function getRealClass ()
{
if ($this -> instance === NULL)
{
$this -> instance = new RealClass ();
}
return $this -> instance;
}
public function doFoo ()
{
return $this -> getRealClass () -> doFoo ();
}
public function isFoo ()
{
return $this -> getRealClass () -> isFoo ();
}
// etc
}
Run Code Online (Sandbox Code Playgroud)
因为代理具有与真实类相同的接口,所以可以将其作为参数传递给任何为接口键入提示的函数/方法.Liskov替换原则适用于代理,因为它响应所有与真实类相同的消息并返回相同的结果(接口强制执行此操作,至少对于方法签名).但是,除非实际将消息发送到代理,否则实际类不会被实例化,这会在后台对实际类进行惰性实例化.
function sendMessageToRealClass (MyInterface $instance)
{
$instance -> doFoo ();
}
sendMessageToRealClass (new RealClass ());
sendMessageToRealClass (new RealClassProxy ());
Run Code Online (Sandbox Code Playgroud)
代理对象涉及一个额外的间接层,这显然意味着每个方法调用都会产生很小的性能影响.但是,它确实允许您进行延迟实例化,因此您可以避免实例化您不需要的类.这是否值得,取决于实例化实际对象的成本与额外的间接层的成本.
编辑:我最初编写这个答案的想法是子类化真实对象,所以你可以使用这个技术与没有实现任何接口的对象,如PDO.我原本以为接口是正确的方法,但我想要一种不依赖于类与接口绑定的方法.在反思这是一个很大的错误,所以我更新了答案,以反映我本来应该做的事情.但是,此版本的确意味着您无法直接将此技术应用于没有关联接口的类.您必须将这些类包装在另一个类中,该类确实为代理方法提供了可行的接口,这意味着另一层间接.