[ Поиск ] - [ Пользователи ] - [ Календарь ]
Полная Версия: Колупаем DI Container
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
twin
Немного теории. За одно по паттернам побежимся.

Эволюция развития IoC (Inversion of Control).

DI (Dependency Injection), это по сути передача объекта параметром.
Вот элементарный пример. Допустим есть класс-служба. Или сервис. Вобщем то, что часто нужно:
class Example
{
public function run()
{
echo 'cработало ';
}
}

(new Example)->run();

Теперь есть второй класс, использующий его:
class Example
{
public function run()
{
echo 'cработало ';
}
}


class UsingClass
{
public function __construct()
{
$obj = new Example;
echo 'и тут ';
$obj->run();
}
}


(new UsingClass);



Но тут возникает проблема. Хардкод. Класс UsingClass жестко зависит от Example. Если потребуется изменить его (допустим для юнит-тестирования), то придется лезть в код. А это нарушение SOLID. А классов, использующих Example может быть много, и это будет адъ и израиль.


Решается это созданием фабрики:

// Фабрика
class Factory
{
public $container = [];

public function __construct()
{
$this->container['Example'] = new Example;
// $this->container['SomeClass'] = new SomeClass;
// $this->container['AnotherClass'] = new AnotherClass;

}

public function get($class)
{
return new $this->container[$class];
}
}


class Example
{
public function run()
{
echo 'cработало ';
}
}


class UsingClass
{
public function __construct()
{
$obj = new Factory;
echo 'и тут ';
$obj->get('Example')->run();
}
}


(new UsingClass);

Уже лучше получилось. Подменить классы можно в одном месте. Однако в проекте может быть несколько разных фабрик. Инициализировать их все в методе, значит неимоверно утяжелять код. Можно создать фабрику фабрик. Это весело. :)

Кроме того, хардкод остался, хоть и в одном месте. Подменить класс не получится, без модификации кода класса, а это опять не по правилам. Но есть другое решение.

Легким движением руки фабрика превращается в абстрактную фабрику.

// Абстрактная фабрика
class AbstractFactory
{
public $container = [];

public function set($class)
{
$this->container[$class] = new $class;
}

public function get($class)
{
return new $this->container[$class];
}
}


class Example
{
public function run()
{
echo 'cработало ';
}
}


class UsingClass
{
public function __construct()
{
$obj = new AbstractFactory;
echo 'и тут ';
$obj->set('Example');
$obj->get('Example')->run();
}
}


(new UsingClass);

Теперь можно подменить контейнер, и все классы одним махом заменятся на юнит-тесты допустим. Однако в таком случае мы опять нарушим принцип SOLID. Потому что для этого всё равно нужно будет залазить в код класса фабрики.

И вот тут наступает очередь DI. Мы отправляем весь объект фабрики в класс из контекста:
class AbstractFactory
{
public $container = [];

public function instance($class)
{
$this->container[$class] = new $class;
}

public function get($class)
{
return $this->container[$class];
}
}


class Example
{
public function run()
{
echo 'cработало ';
}
}


class UsingClass
{
public function __construct($factory)
{
echo 'и тут ';
$factory->instance('Example');
$factory->get('Example')->run();
}
}


//////////////////////////////////////////
//Отправляем весь объект фабрики прямо в конструктор.
(new UsingClass(new AbstractFactory));


Это называется "внедрение зависимости через конструктор", или "Constructor Injection". Еще можно внедрять объекты через свойства или сеттеры. Не суть. Суть в том, что теперь не нужно трогать сами классы. Достаточно подменить всю фабрику при вызове.

Но все равно код получается сильно связанным. Опять нарушен один из пунктов SOLID. Потому что работа UsingClass зависит от конкретных классов. А хотелось бы повторного использования.

Так вот, раз мы отправляем эту фабрику из контекста, то можно и настроить её в том же месте. А это мы полную бочку туда катим. Это уже контейнерная перевозка получается. :)

Такая контейнерная перевозка называется DI-контейнер. Самая простая реализация:

class DiContainer
{
public $container = [];

public function set($key, $class)
{
$this->container[$key] = $class;
}

public function get($key)
{
return new $this->container[$key];
}
}


class Example
{
public function run()
{
echo 'cработало ';
}
}


class UsingClass
{
public function __construct($container)
{
echo 'и тут ';
$container->get('work')->run();
}
}


//////////////////////////////////////////

$container = new DiContainer;
$container->set('work', 'Example');

(
new UsingClass($container));

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

Но не все так гладко. Завтра продолжу.

_____________
Если вам недостаточно собственных заблуждений, можно расширить их мнениями экспертов.

Нужно уважать мнение оппонета. Ведь заблуждаться - его святое право.

Настаивал, настаиваю и буду настаивать на своем. На кедровых орешках.

user posted image
Быстрый ответ:

 Графические смайлики |  Показывать подпись
Здесь расположена полная версия этой страницы.
Invision Power Board © 2001-2024 Invision Power Services, Inc.