Что такое контейнер для инъекций зависимостей?

Я пытаюсь понять роль контейнера для инъекций зависимостей, потому что это ставит меня как основной в поддерживаемом коде.

Насколько я понимаю, DIC соответствует названию: контейнер, в котором все ваши зависимости собраны вместе. Вместо того, чтобы видеть new Foo\Bar по всему приложению, все новые экземпляры генерируются внутри контейнера, а затем передаются друг другу, где они необходимы (например, экземпляр Model создается экземпляром Database , который создается с помощью Экземпляр 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; } } 

Вопросов

  • Могла ли моя реализация выше, хотя и очень простая, классифицироваться как базовый инжектор зависимостей?
  • Является ли контейнер для инъекций зависимостей обычно состоящим из одного класса?

Примечание: первые три заголовка отвечают на ваши вопросы, в то время как следующие ответы отвечают на ожидаемые вопросы и обеспечивают освещение чего-либо в первых двух разделах.

Может ли это быть классифицировано как контейнер для инъекций зависимостей?

Нет, это не похоже на контейнер для инъекций зависимостей. Контейнер инъекции зависимостей предназначен для сокращения работы, требуемой для создания, путем определения, создания и ввода всех зависимостей. Скорее всего, у вас есть комбинация фабрики и локатора сервисов.

Заводы абстрактно создают объекты. Это то, что делает ваш класс Container . newModel назначенные методы (т. newModel ), ваш контейнер берет на себя ответственность за поиск точного объекта, который будет создан, и создание экземпляра этого объекта.

Причина, по которой я назвал бы это «бедной» фабрикой, заключается в том, что она начинает выглядеть так, как будто ее можно использовать для поиска сервисов. Локаторы служб работают, скрывая зависимости объекта: вместо того, чтобы быть зависимым от GenericService , объект может зависеть от локатора службы. С учетом локатора службы он может запросить экземпляр GenericService . Я вижу, что подобное поведение начинает задерживаться в методах add() и getInstance() . Локаторы сервисов обычно считаются анти-шаблонами, потому что они абстрагируют зависимости, поэтому код невозможно проверить!

Является ли контейнер инъекций зависимостей состоящим из одного класса?

Это зависит. Вы можете легко создать простой контейнер для инъекций с одним классом. Проблема заключается в том, что характер простого контейнера имеет тенденцию к более продвинутому в непростой контейнер. Когда вы начинаете улучшать свой шаблон, вам нужно подумать о том, как разные компоненты играют вместе. Спросите себя: соблюдают ли они принципы SOLID? Если нет, рефакторинг необходим.

Что такое контейнер для инъекций зависимостей?

Я сказал это выше, но опять же: контейнер инъекции зависимостей предназначен для сокращения работы, требуемой для создания, определения, создания и ввода всех зависимостей. DIC будет рассматривать все зависимости класса и всех зависимостей, которые могут иметь эти зависимости, и т. Д. В этом смысле контейнер отвечает за иерархическое создание экземпляров всех зависимостей .

Класс Container вы предоставляете, основывается на очень строгих определениях предопределенных классов. Например, классы на вашем уровне модели зависят только от соединения с базой данных. (Аналогичные утверждения можно сказать о классах на вашем уровне контроллера и просмотра).

Как контейнер зависимостей для инъекций находит зависимости?

Контейнер инъекции зависимостей будет определять зависимости. Обычно это происходит через 1 из 3 механизмов: автоувеличивание, аннотации и определения. Документы PHP-DI дают хорошее представление о том, что здесь все трое. Короче говоря: autwiring обнаруживает зависимости, отражая класс, аннотации используются для записи в зависимостях с использованием комментариев над классом, а определения используются для зависимостей жесткого кода. Лично я предпочитаю autowiring, потому что он чист и прост.

Могу ли я создать простой контейнер инъекций зависимостей или нет?

Да, ты можешь. Начните с идеи, что инжектор должен иметь возможность создавать экземпляр любого объекта (службы, представления, контроллера и т. Д.). Он должен смотреть на соответствующий объект и иерархически создавать экземпляры всех зависимостей (подсказка: возможно, с помощью некоторого метода рекурсии).

Быстрый пример простого инжектора с использованием автоустановки выглядит следующим образом:

 <?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(); 

У этого основного инжектора есть очевидные недостатки. Например, есть возможность создать рекурсию, если два класса зависят друг от друга (для этого должна быть проверка). Однако, как это, это работает как пример того, как выглядит инжектор.

Контейнер для инъекций против инжекции

Чтобы сделать это более мощным и подпадать под определение «контейнера инъекций зависимостей», вам нужен способ обмена экземплярами экземпляров через несколько вызовов make() . Например, у вас может быть другой метод, называемый share() . Этот метод сохранит экземпляр, переданный ему. Всякий раз, когда класс создается методом make() и зависит от ранее разделяемого класса, вместо создания экземпляра нового экземпляра он будет использовать уже созданный экземпляр.

Для простого и мощного контейнера для инъекций зависимостей я предлагаю Auryn , но, во всяком случае, попытайтесь понять и создать свой собственный, прежде чем использовать те, которые уже доступны.