什么是依赖注入容器?

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;
    }
}

问题

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,但无论如何,在使用现有的容器之前,请尝试理解并创建自己的容器。