Моё понимание сути depency injection. Тезисно.
Все классы вызываются через метод класса DI.
Атрибут класса DI содержит список зависимостей классов.
Внутри DI, классы чьи зависимости описаны, вызываются так же - через DI, реализуя их все.
DI работает по принципу ленивой загрузки. То есть, создает объекты только при необходимости, все остальное время только хранит соответствия интерфейсов реализациям и свои внутренние настройки.
Пример работы DI
Составляю список (массив) соответствия классов и их зависимостей.
При попытке создания объекта через DI (условно $DI->create('
amespace\ClassName') вместо new
amespace\ClassName), проверяю наличие зависимостей в массиве для вызываемого класса и если необходимо, подменяю определенные аргументы, необходимыми классами в соответствии с правилами из массива. После этого возвращаю созданный объект.
Правильно ли я понял алгоритм работы depency injection?
Если есть конструктивные замечания, поправьте (желательно с ссылками).
p.s. понимание принципа работы составил после прочтения этого
Ответ
DI не обязательно работает по принципу ленивой загрузки. Ленивая загрузка - это оптимизация уже сверх того, что призвано делать внедрение зависимостей
Внедрение зависимостей - это прежде всего абстрактная идея, которая не имеет какой-то единственно правильной реализации в коде. Суть идеи в отказе от управлении зависимостями самими объектами. То есть, обычно, в отказе от создания самим объектом экземпляров каких-то классов, которые нужны этому объекту для работы.
Если ваш объект сам создаёт какие-то классы, а не получает их извне готовыми, то это уже не внедрение зависимостей.
Например, у вас есть объект корзины, которому необходимо что-то сохранять в БД.
Если не использовать DI, то объекту корзины самому нужно будет получить откуда-то объект DAO чтобы сохранить что-то в БД. Тут вы сразу сталкиваетесь с необходимостью использовать какой-то синглтон, и с очевидными проблемами при тестировании объекта корзины в отвязке от какой-то БД. Объекту корзины придётся знать что за такие объекты DAO, с которыми он работает.
В случае использования DI объект корзины по умолчанию не пытается создать объект, а ожидает (и предоставляет методы) что ему укажут извне, при или сразу после инициализации, куда и к какому объекту нужно обращаться для добавления записей в БД.
Такой подход даёт много преимуществ как упрощая сами классы, так и упрощая тестирования и изменение. Если вам когда-нибудь захочется использовать совсем другой объект DAO, то для этого вам не нужно будет менять объект корзины, при условии, конечно, что тот ждёт на вход интерфейс, а не конкретный объект.
Конечно, всё имеет свою цену. Программы, использующие DI, становятся более сложными для понимания, так как та логика, которая раньше была в одном месте, оказывается раскидана по всей программе. Стоит ли платить эту цену, и так ли вам нужны все достоинства этого подхода - решать нужно взвешенно по фактической ситуации. Для каких-то простых программ можно обойтись без DI вообще. На стадии прототипа тоже может и не нужно никакое DI. Если же вы хотите сделать максимально усложнить понимание вашей программы, то использование DI обязательно во всех ситуациях
Родственным для DI паттерном является паттерн MVC. Идея MVC несколько более конкретная, потому что в MVC подразумеваются конкретные роли у составных частей. Базу данных вы прячете за контроллером, а корзина ничего не знает о БД, но знает к какому контроллеру её следует обращаться в случае каких-то событий. То есть, вы тоже приходите к тому что внедряете в объект корзины её зависимость в виде контроллера.
Практический пример
У вас есть интерфейс для получения и сохранения каких-то данных.
interface DAO
{
public function save($data);
public function load();
}
У вас есть объект корзины, который ждёт что при инициализации ему сообщат куда нужно обращаться за данными, и куда сохранять.
class Cart
{
private $dao;
private $items = [];
public function __construct(DAO $dao)
{
$this->dao = $dao;
$this->items = $this->dao->load();
}
public function add($item, $qty = 1)
{
$this->items[$item] += $qty;
$this->dao->save($this->items)
}
public function delete($item)
{
$this->items[$item] = 0;
$this->dao->save($this->items)
}
/* Другие обычные методы пропустим */
}
У вас есть конкретная реализация интерфейса.
class SessionDAO implements DAO
{
/* Методы инициализации придумайте сами */
public function save($data)
{
// куда-то сохраняете
}
public function load()
{
// откуда-то загружаете
}
}
Теперь вы можете сами в правильной последовательности создать объект DAO, передав его объекту корзины при инициализации.
$dao = new SessionDAO();
$dao->initWithKey(/* ... */);
$cart = new Cart($dao);
$cart->add('Example', 10);
Как видите, в корзине нигде не инициализируются объекты, которые нужны корзине для работы. Все необходимые объекты корзина получает из внешнего мира.
Нет никакой причины, которая помешала бы вам сделать ленивое DAO, которое инициализировалось только в момент использования:
class LazySessionDAO implements DAO
{
private $dao;
private function getConcreteDAO()
{
// отложенно инициализируем DAO при необходимости
if (!$this->dao) {
$this->dao = new SessionDAO();
$this->dao->initWithKey(/* ... */);
}
return $this->dao;
}
public function save($data)
{
$this->getConcreteDAO()->save($data);
}
public function load()
{
$this->getConcreteDAO()->load();
}
}
Как видите, для этого ровным счётом ничего не нужно было менять в объекте корзины. Именно потому внедрение зависимостей так любят и ценят.
Теперь к упомянутому в статье Phemto. Это - dependency injection container или dependency injector, а не просто dependency injection. Один из многих.
Комментариев нет:
Отправить комментарий