Понимание контейнеров IoC и инъекций зависимостей

Быстрый переход:

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

На данный момент я пробовал прочитать документацию для различных фреймворков (laravel, fuel, codeigniter, symfony), и я обнаружил, что существует слишком много различных аспектов рамок, которые мне нужно было чувствовать удобными, используя это, что я решил попробовать просто изучите каждую из основных частей самостоятельно самостоятельно, прежде чем пытаться использовать их в самих рамках.

Я потратил несколько часов на поиски различных значений, просматривая ответы stackoverflow и читаю различные статьи, пытаясь понять, что такое IoC и как использовать его для правильного управления зависимостями, и я считаю, что понимаю, что это такое в концепции, но я все еще серый о том, как правильно его реализовать. Я думаю, что лучший способ для любого, кто читает это, чтобы помочь мне, – это дать то, что мое нынешнее понимание контейнеров IoC и инъекции зависимостей, а затем позволить людям, которые лучше понимают, чем я, указывают на то, что мое понимание не оправдалось.

Мое понимание:

  • Зависимость – это когда экземпляр класса ClassA требует экземпляра ClassB для создания экземпляра класса ClassA.
  • Инъекция зависимостей – это когда ClassA передается экземпляр класса B либо через параметр в конструкторе ClassA, либо через функцию set DependencyNameHere ~ (~ DependencyNameHere ~ $ param). (Это одна из тех областей, на которых я не совсем уверен) .
  • Контейнер IoC – это одноэлементный класс (может быть создан только один экземпляр экземпляра в любой момент времени), где можно зарегистрировать конкретный способ создания объектов этого класса для этого проекта. Вот ссылка на пример того, что я пытаюсь описать вместе с определением класса для контейнера IoC, который я использовал

Поэтому на этом этапе я начинаю пытаться использовать контейнер IoC для более сложных сценариев. На данный момент кажется, что для использования контейнера IoC я ограничен отношением has-a для почти любого класса, который я хочу создать, который имеет зависимости, которые он хочет определить в контейнере IoC. Что делать, если я хочу создать класс, который наследует класс, но только если родительский класс был создан определенным образом, он был зарегистрирован в контейнере IoC.

Например, я хочу создать дочерний класс mysqli, но я хочу зарегистрировать этот класс в контейнере IoC только для создания экземпляра родительского класса, сконструированного таким образом, который я ранее зарегистрировал в контейнере IoC. Я не могу придумать способ сделать это без дублирования кода (и поскольку это учебный проект, я стараюсь, чтобы он был «чистым», насколько это возможно). Вот еще несколько примеров того, что я пытаюсь описать.

Вот некоторые из моих вопросов:

  • Является ли то, что я пытаюсь сделать выше возможного, не нарушая какого-либо принципа ООП? Я знаю, что в c ++ я мог использовать динамическую память и конструктор копирования для ее выполнения, но я не смог найти такую ​​функциональность в php. (Я признаю, что у меня очень мало опыта использования каких-либо других магических методов, кроме __construct, но из чтения и __clone, если я правильно понял, я не мог использовать в конструкторе его, чтобы сделать дочерний класс экземпляром клона экземпляр родительского класса).
  • Где все мои определения классов зависимостей относятся к IoC? (Если мой IoC.php просто имеет кучу require_once ('dependencyClassDefinition.php') наверху? Моя реакция на кишок состоит в том, что есть лучший способ, но я еще не придумал один)
  • В каком файле я должен регистрировать свои объекты? Выполняя все вызовы IoC :: register () в файле IoC.php после определения класса.
  • Нужно ли регистрировать зависимость в IoC, прежде чем регистрировать класс, который нуждается в этой зависимости? Поскольку я не вызываю анонимную функцию, пока я фактически не создам объект, зарегистрированный в IoC, я предполагаю, что нет, но его все еще вызывает беспокойство.
  • Есть ли что-то еще, что я забываю, что я должен делать или использовать? Я пытаюсь сделать это шаг за шагом, но я также не хочу знать, что мой код будет многоразовым и, самое главное, что кто-то, кто ничего не знает о моем проекте, может его прочитать и понять.

Я знаю, что это очень долго, и просто хотел заранее поблагодарить любого, кто нашел время, чтобы прочитать его, и тем более, чтобы кто-то делился своими знаниями.

Проще говоря (потому что это не проблема, ограниченная только для мира ООП), зависимость – это ситуация, когда компонент А нуждается (зависит от) компонента В, чтобы делать вещи, которые он должен делать. Это слово также используется для описания зависимого компонента в этом сценарии. Чтобы поместить это в ООП / PHP, рассмотрите следующий пример с обязательной аналогией автомобилей:

class Car { public function start() { $engine = new Engine(); $engine->vroom(); } } 

Car зависит от Engine . Engineзависимость от автомобиля . Этот фрагмент кода довольно плох, потому что:

  • зависимость неявная; вы не знаете, что это там, пока вы не проверите код автомобиля
  • классы тесно связаны; вы не можете заменить Engine MockEngine для целей тестирования или TurboEngine который расширяет исходный, без изменения Car .
  • Кажется, это глупо для автомобиля, чтобы он мог построить двигатель для себя, не так ли?

Включение зависимостей – это способ решения всех этих проблем, поскольку тот факт, что Car нуждается в Engine и явно предоставляет ему один:

 class Car { protected $engine; public function __construct(Engine $engine) { $this->engine = $engine; } public function start() { $this->engine->vroom(); } } $engine = new SuperDuperTurboEnginePlus(); // a subclass of Engine $car = new Car($engine); 

Вышеприведенный пример инсталляции конструктора , в котором зависимость (объект, зависящий от объекта) предоставляется зависимому (потребителю) через конструктор класса. Другой способ – разоблачить метод setEngine в классе Car и использовать его для ввода экземпляра Engine . Это известно как инъекция сеттера и полезно в основном для зависимостей, которые должны быть заменены во время выполнения.

Любой нетривиальный проект состоит из кучи взаимозависимых компонентов, и становится легко потерять следы от того, что вводится там довольно быстро. Контейнер инъекции зависимостей – это объект, который знает, как создавать и настраивать другие объекты, знает, какова их связь с другими объектами в проекте, и делает инъекцию зависимостей для вас. Это позволяет централизовать управление всеми зависимостями вашего проекта (inter) и, что более важно, позволяет изменять / издеваться над одним или несколькими из них без необходимости редактировать кучу мест в вашем коде.

Давайте сравним аналогию с автомобилем и посмотрим, какие попытки OP пытаются достичь в качестве примера. Допустим, у нас есть объект Database зависимости от объекта mysqli . Предположим, мы хотим использовать действительно примитивный класс контейнера индексов индексов зависимости DIC который предоставляет два метода: register($name, $callback) чтобы зарегистрировать способ создания объекта под заданным именем и resolve($name) чтобы получить объект из это имя. Наша настройка контейнера будет выглядеть примерно так:

 $dic = new DIC(); $dic->register('mysqli', function() { return new mysqli('somehost','username','password'); }); $dic->register('database', function() use($dic) { return new Database($dic->resolve('mysqli')); }); 

Обратите внимание, что мы говорим нашему контейнеру, чтобы получить экземпляр mysqli от себя, чтобы собрать экземпляр Database . Затем, чтобы получить экземпляр Database с автоматически зависящей зависимостью, мы просто:

 $database = $dic->resolve('database'); 

В этом суть. Несколько более сложным, но все же относительно простым и легким для понимания контейнера PHP DI / IoC является Pimple . Проверьте его документацию для получения дополнительных примеров.


Что касается кода и вопросов OP:

  • Не используйте статический класс или синглтон для вашего контейнера (или для чего-нибудь еще, если на то пошло); они оба злые . Вместо этого проверьте Пимл.
  • Решите, хотите ли вы, чтобы ваш класс mysqliWrapper расширил mysql или зависел от него.
  • mysqliWrapper IoC из mysqliWrapper вы mysqliWrapper одну зависимость для другой. Ваши объекты не должны знать или использовать контейнер; в противном случае это не DIC, это шаблон Service Locator (анти).
  • Вам не нужно require файл класса, прежде чем регистрировать его в контейнере, так как вы не знаете, собираетесь ли вы вообще использовать объект этого класса. Сделайте все настройки вашего контейнера в одном месте. Если вы не используете автозагрузчик, вы можете require внутри анонимной функции, которую вы регистрируете в контейнере.

Дополнительные ресурсы:

  • Инверсия контрольных контейнеров и модель впрыска зависимостей Мартина Фаулера
  • Не смотрите на вещи – Чистый код. Разговор о IoC / DI