什么是依赖注入容器?
What is a Dependency Injection Container?
我正在尝试了解依赖注入容器的作用,因为它让我觉得它是可维护代码的基础。
据我了解,DIC正如标题所暗示的那样:一个将所有依赖项收集在一起的容器。所有新实例都在容器内部生成,然后在需要它们的地方相互传递,而不是在整个应用程序中看到 new Foo\Bar
(例如,Model
是用 [= 的实例实例化的13=],用 Config
).
的实例实例化
我试图制作一个非常简单的 DIC。这是结果。
在我的前端控制器中,我正在实例化一个新的 App\Core\Container
。
我的 Container
看起来像这样:
<?php
namespace App\Core;
use App\Config;
class Container
{
public $config;
public $router;
public $database;
public $model;
public $view;
public $controller;
public function __construct()
{
$this->config = new Config;
$this->router = new Router;
$this->database = new Database($this->config);
}
public function add()
{
// add dependencies from the outside?
}
public function getInstance(/* string */)
{
// return an instance for use somewhere?
}
public function newModel($model)
{
$model = $this->getModelNamespace() . $model;
$this->model = new $model($this->database);
return $this->model;
}
private function getModelNamespace()
{
$namespace = 'App\Models\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\';
}
return $namespace;
}
public function newView($params)
{
$this->view = new View($this->model, $params);
return $this->view;
}
public function newController($controller)
{
$controller = $this->getControllerNamespace() . $controller;
$this->controller = new $controller;
return $this->controller;
}
private function getControllerNamespace()
{
$namespace = 'App\Controllers\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\';
}
return $namespace;
}
}
问题
- 我上面的实现虽然很简单,但可以class作为基本的依赖注入器吗?
- 依赖注入容器一般由一个class组成吗?
Note: the first three headings answer your questions, while the following ones answer anticipated questions and provide coverage of anything in the first two sections.
这可以class化为依赖注入容器吗?
不,这看起来不像是依赖注入容器。依赖项注入容器旨在通过确定、创建和注入所有依赖项来减少实例化所需的工作。 而你所拥有的似乎是工厂和服务定位器的组合.
Factories抽象对象的创建。这基本上就是您的 Container
class 正在做的事情。通过调用指定的方法(即 newModel
),您的容器负责定位要实例化的确切对象并构造该对象的实例。
我将其称为“糟糕”工厂的原因是,它开始看起来像是可以用来定位服务。 服务定位器通过隐藏对象的依赖关系来工作:对象可能不依赖于GenericService
,而是依赖于服务定位器。给定服务定位器,它可以请求 GenericService
的实例。我看到类似的行为开始在您的 add()
和 getInstance()
方法中发挥作用。服务定位器通常被认为是反模式,因为它们抽象了依赖关系,因此使代码无法测试!
一个依赖注入容器是由一个class组成的吗?
这取决于。 你可以很容易地用一个 class 制作一个简单的依赖注入容器。问题是 简单 容器的性质往往会变得更高级 不那么简单 容器。当您开始改进您的模式时,您需要考虑不同的组件如何协同工作。问问自己:他们是否遵循 SOLID 原则?如果不是,则需要重构。
什么是依赖注入容器?
我在上面说过,但再说一遍:依赖注入容器旨在通过确定、创建和注入所有依赖项来减少实例化所需的工作。 DIC 将查看 class 的所有依赖项,以及这些依赖项可能具有的所有依赖项,依此类推...从这个意义上说,容器负责 分层实例化所有依赖项 .
您提供的 Container
class 依赖于对预定义 class 的非常严格的定义。例如,模型层中的 classes 似乎 仅 依赖于数据库连接。 (关于控制器和视图层中的 classes 可以说类似的陈述)。
依赖注入容器如何找到依赖?
依赖项注入容器将检测依赖项。这通常通过以下 3 种机制中的一种发生:自动装配、注释和定义。 PHP-DI 文档很好地说明了 here. In short, though: autowiring detects dependencies by reflecting 在 class 上所有这三个内容的含义,注释用于使用 class 上方的注释写入依赖项,并且定义用于硬编码依赖项。就个人而言,我更喜欢自动装配,因为它简洁明了。
我可以创建一个简单的依赖注入容器吗?
是的,你可以。从注入器应该能够实例化任何对象(服务、视图、控制器等)的想法开始。它需要查看相关对象并分层实例化所有依赖项(提示:可能通过某种递归方法)。
使用自动装配的简单注入器的快速示例如下所示:
<?php
class Injector
{
public function make($className)
{
$dependencies = [];
//Create reflection of the class-to-make's constructor to get dependencies
$classReflection = new ReflectionMethod($className, "__construct");
foreach($classReflection->getParameters() as $parameter) {
$dependencyName = $parameter->getClass()->getName();
//Use the injector to make an instance of the dependency
$dependencies[] = $this->make($dependencyName);
}
$class = new ReflectionClass($className);
//Instantiate the class with all dependencies
return $class->newInstanceArgs($dependencies);
}
}
用类似下面的东西进行测试,你可以看到注入器是如何递归检查和实例化所有依赖项的
class A {
protected $b;
public function __construct(B $b) { $this->b = $b; }
public function output(){ $this->b->foo(); }
}
class B {
protected $c;
public function __construct(C $c) { $this->c = $c; }
public function foo() { $this->c->bar(); }
}
class C {
public function __construct() { }
public function bar() { echo "World!"; }
}
$injector = new Injector;
$a = $injector->make("A");
//No need to manually instantiate A's dependency, B, or B's dependency, C
$a->output();
这个基本的喷油器有明显的故障。例如,如果两个 classes 相互依赖(应该对此进行检查),则有可能造成递归灾难。但是,按原样,这可以作为注射器外观的 基本 示例。
注入器与依赖注入容器
为了使其更强大并符合“依赖注入容器”的定义,您需要一种在多个 make()
调用之间共享实例化实例的方法。例如,您可能有另一个名为 share()
的方法。此方法将存储传递给它的实例。每当通过 make()
方法构建 class 并依赖于先前共享的 class 时,它不会实例化新实例,而是使用已经实例化的实例。
对于简单而强大的依赖项注入容器,我建议 Auryn,但无论如何,在使用现有的容器之前,请尝试理解并创建自己的容器。
我正在尝试了解依赖注入容器的作用,因为它让我觉得它是可维护代码的基础。
据我了解,DIC正如标题所暗示的那样:一个将所有依赖项收集在一起的容器。所有新实例都在容器内部生成,然后在需要它们的地方相互传递,而不是在整个应用程序中看到 new Foo\Bar
(例如,Model
是用 [= 的实例实例化的13=],用 Config
).
我试图制作一个非常简单的 DIC。这是结果。
在我的前端控制器中,我正在实例化一个新的 App\Core\Container
。
我的 Container
看起来像这样:
<?php
namespace App\Core;
use App\Config;
class Container
{
public $config;
public $router;
public $database;
public $model;
public $view;
public $controller;
public function __construct()
{
$this->config = new Config;
$this->router = new Router;
$this->database = new Database($this->config);
}
public function add()
{
// add dependencies from the outside?
}
public function getInstance(/* string */)
{
// return an instance for use somewhere?
}
public function newModel($model)
{
$model = $this->getModelNamespace() . $model;
$this->model = new $model($this->database);
return $this->model;
}
private function getModelNamespace()
{
$namespace = 'App\Models\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\';
}
return $namespace;
}
public function newView($params)
{
$this->view = new View($this->model, $params);
return $this->view;
}
public function newController($controller)
{
$controller = $this->getControllerNamespace() . $controller;
$this->controller = new $controller;
return $this->controller;
}
private function getControllerNamespace()
{
$namespace = 'App\Controllers\';
if (array_key_exists('namespace', $this->params = [])) {
$namespace .= $this->params['namespace'] . '\';
}
return $namespace;
}
}
问题
- 我上面的实现虽然很简单,但可以class作为基本的依赖注入器吗?
- 依赖注入容器一般由一个class组成吗?
Note: the first three headings answer your questions, while the following ones answer anticipated questions and provide coverage of anything in the first two sections.
这可以class化为依赖注入容器吗?
不,这看起来不像是依赖注入容器。依赖项注入容器旨在通过确定、创建和注入所有依赖项来减少实例化所需的工作。 而你所拥有的似乎是工厂和服务定位器的组合.
Factories抽象对象的创建。这基本上就是您的 Container
class 正在做的事情。通过调用指定的方法(即 newModel
),您的容器负责定位要实例化的确切对象并构造该对象的实例。
我将其称为“糟糕”工厂的原因是,它开始看起来像是可以用来定位服务。 服务定位器通过隐藏对象的依赖关系来工作:对象可能不依赖于GenericService
,而是依赖于服务定位器。给定服务定位器,它可以请求 GenericService
的实例。我看到类似的行为开始在您的 add()
和 getInstance()
方法中发挥作用。服务定位器通常被认为是反模式,因为它们抽象了依赖关系,因此使代码无法测试!
一个依赖注入容器是由一个class组成的吗?
这取决于。 你可以很容易地用一个 class 制作一个简单的依赖注入容器。问题是 简单 容器的性质往往会变得更高级 不那么简单 容器。当您开始改进您的模式时,您需要考虑不同的组件如何协同工作。问问自己:他们是否遵循 SOLID 原则?如果不是,则需要重构。
什么是依赖注入容器?
我在上面说过,但再说一遍:依赖注入容器旨在通过确定、创建和注入所有依赖项来减少实例化所需的工作。 DIC 将查看 class 的所有依赖项,以及这些依赖项可能具有的所有依赖项,依此类推...从这个意义上说,容器负责 分层实例化所有依赖项 .
您提供的 Container
class 依赖于对预定义 class 的非常严格的定义。例如,模型层中的 classes 似乎 仅 依赖于数据库连接。 (关于控制器和视图层中的 classes 可以说类似的陈述)。
依赖注入容器如何找到依赖?
依赖项注入容器将检测依赖项。这通常通过以下 3 种机制中的一种发生:自动装配、注释和定义。 PHP-DI 文档很好地说明了 here. In short, though: autowiring detects dependencies by reflecting 在 class 上所有这三个内容的含义,注释用于使用 class 上方的注释写入依赖项,并且定义用于硬编码依赖项。就个人而言,我更喜欢自动装配,因为它简洁明了。
我可以创建一个简单的依赖注入容器吗?
是的,你可以。从注入器应该能够实例化任何对象(服务、视图、控制器等)的想法开始。它需要查看相关对象并分层实例化所有依赖项(提示:可能通过某种递归方法)。
使用自动装配的简单注入器的快速示例如下所示:
<?php
class Injector
{
public function make($className)
{
$dependencies = [];
//Create reflection of the class-to-make's constructor to get dependencies
$classReflection = new ReflectionMethod($className, "__construct");
foreach($classReflection->getParameters() as $parameter) {
$dependencyName = $parameter->getClass()->getName();
//Use the injector to make an instance of the dependency
$dependencies[] = $this->make($dependencyName);
}
$class = new ReflectionClass($className);
//Instantiate the class with all dependencies
return $class->newInstanceArgs($dependencies);
}
}
用类似下面的东西进行测试,你可以看到注入器是如何递归检查和实例化所有依赖项的
class A {
protected $b;
public function __construct(B $b) { $this->b = $b; }
public function output(){ $this->b->foo(); }
}
class B {
protected $c;
public function __construct(C $c) { $this->c = $c; }
public function foo() { $this->c->bar(); }
}
class C {
public function __construct() { }
public function bar() { echo "World!"; }
}
$injector = new Injector;
$a = $injector->make("A");
//No need to manually instantiate A's dependency, B, or B's dependency, C
$a->output();
这个基本的喷油器有明显的故障。例如,如果两个 classes 相互依赖(应该对此进行检查),则有可能造成递归灾难。但是,按原样,这可以作为注射器外观的 基本 示例。
注入器与依赖注入容器
为了使其更强大并符合“依赖注入容器”的定义,您需要一种在多个 make()
调用之间共享实例化实例的方法。例如,您可能有另一个名为 share()
的方法。此方法将存储传递给它的实例。每当通过 make()
方法构建 class 并依赖于先前共享的 class 时,它不会实例化新实例,而是使用已经实例化的实例。
对于简单而强大的依赖项注入容器,我建议 Auryn,但无论如何,在使用现有的容器之前,请尝试理解并创建自己的容器。