我刚开始使用依赖注入时出于显而易见的原因,并且在没有阅读控制反转(IoC)的情况下很快发现了在实例化我的某些类时出现冗长的问题。所以,阅读IoC我有一个问题没有找到具体的答案。课程注册何时发生?在引导程序?执行前?如何强制执行依赖项的类型?
我没有使用任何框架。为了学习,我写了自己的容器。
这是我的容器和一些示例类的一个非常低级的例子。
class DepContainer
{
private static $registry = array();
public static function register($name, Closure $resolve)
{
self::$registry[$name] = $resolve;
}
public static function resolve($name)
{
if (self::registered($name)) {
$name = static::$registry[$name];
return $name();
}
throw new Exception('Nothing bro.');
}
public static function registered($name)
{
return array_key_exists($name, self::$registry);
}
}
class Bar
{
private $hello = 'hello world';
public function __construct()
{
# code...
}
public function out()
{
echo $this->hello . "\n";
}
}
class Foo
{
private $bar;
public function __construct()
{
$this->bar = DepContainer::resolve('Bar');
}
public function say()
{
$this->bar->out();
}
}
这些已经在app结构中了。 Dependecy Injection方式我会输入提示输入参数,但没有它我可以做:
DepContainer::register('Bar', function(){
return new Bar();
});
$f = new Foo();
$f->say();
对我来说,在一个bootsrap寄存器中有意义的所有依赖关系,它将是IMO更干净的方式。在运行时,就像你所表明的那样,我认为和new Foo(new Bar(...)...)
一样丑陋。
答案 0 :(得分:1)
我将尝试总结一些你应该知道的事情,(希望)将澄清你的一些困境。
让我们从一个基本的例子开始:
class MySQLAdapter
{
public function __construct()
{
$this->pdo = new PDO();
}
}
class Logger
{
public function __construct()
{
$this->adapter = new MySqlAdapter();
}
}
$log = new Logger();
如您所见,我们正在实例化Logger
,它有两个依赖项:MySQLAdapter
和PDO。
此过程的工作方式如下:
上面的代码有效,但如果明天我们决定需要将数据记录在文件而不是数据库中,我们需要
更改Logger
课程并将MySQLAdapter
替换为全新的FileAdapter
。
// not good
class Logger
{
public function __construct()
{
$this->adapter = new FileAdapter();
}
}
这是依赖注入尝试解决的问题:因为依赖关系已更改而不修改类。
Di引用了一个实例化类的过程,它通过赋予它构造函数所需的所有依赖关系来正常运行。如果我们应用依赖 注入我们之前的示例,它将如下所示:
interface AdapterInterface
{
}
class FileAdapter implements AdapterInterface
{
public function __construct()
{
}
}
class MySQLAdapter implements AdapterInterface
{
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
}
class Logger
{
public function __construct(AdapterInterface $adapter)
{
$this->adapter = $adapter;
}
}
// log to mysql
$log = new Logger(
new MySQLAdapter(
new PDO()
)
);
正如您所看到的,我们没有在构造函数中实例化任何内容,但我们将实例化的类传递给构造函数。这允许我们替换任何依赖 没有修改类:
// log to file
$log = new Logger(
new FileAdapter()
);
这有助于我们:
轻松维护代码: 正如您已经看到的,如果其中一个依赖项发生了变化,我们就不需要修改该类。
使代码更易于测试:
当您针对MySQLAdapter
运行测试套件时,您不希望在每次测试时都访问数据库,因此PDO对象在测试中将为mocked:
// test snippet
$log = new Logger(
new MySQLAdapter(
$this->getMockClass('PDO', [...])
)
);
问: Logger
如何知道你给他一个它需要的课而不是一些垃圾?
A:这是接口( AdapterInterface )作业,它是Logger
与其他类之间的合同。记录器"知道"任何实现的类
该特定界面将包含完成其工作所需的方法。
您可以将此类( ie:container )视为存储运行应用程序所需的所有对象的中心位置。当你需要其中一个, 您从容器中请求对象而不是实例化自己。
你可以把DiC视为一只受过训练的狗出去,拿到报纸并把它带回给你。问题是,只有在前门打开的情况下训练了狗。 只要狗的依赖关系不会改变(即开门),一切都会好起来的。如果有一天前门将被关闭,狗将不知道如何拿到报纸。
但如果狗有一个IoC容器,他可以找到一种方法......
正如你所看到的那样," classic"的初始化过程代码是:
IoC简单地复制了上述过程,但顺序相反:
如果您认为依赖注入是某种IoC,那么您是对的。当我们谈到依赖注入时,我们有了这个例子:
// log to mysql
$log = new Logger(
new MySQLAdapter(
new PDO()
)
);
初看起来有人可以说实例化过程是:
问题是代码将从中间向左侧解释。所以订单将是:
IoC容器只是自动执行此过程。当您从容器中请求Logger
时,它使用PHP Reflection和type hinting来分析其依赖项( from constructor ),实例化所有这些,将它们发送到请求的类,并返回一个Logger
实例。
注意:要找出类具有哪些依赖项,一些IoC容器使用annotations而不是类型提示或两者的组合。
所以回答你的问题:
如果您的容器可以自行解决依赖关系,但由于各种原因您还需要手动添加更多依赖关系,您可以在 初始化容器后的引导过程。
注意: 在野外这两个原则之间有各种各样的混合,但我试着向你解释每个原则背后的主要思想是什么。 容器的外观如何仅取决于您,并且不要害怕重新发明轮子,只要您出于教育目的而这样做。