Кратко о внедрение зависимостей и сервис контейнере
Что тако внедрение зависимостей (Dependency Injection) ?
Dependency Injection – это программный шаблон, который реализует принцип объектно-ориентированного программирования "Инверсия управления (Inversion Of Control)". Реализация этого шаблона подразумевает снижения "связанности кода", соответственно, получается код, который легче использовать повторно и сопровождать, то есть, изменение компонент одной части приложения не вызывает ошибок в другой части или необходимости значительных каскадных изменений.
DI существет только в объектно-ориентированном мире. Рассмотрение будет проведено на примере языка PHP. Допустим, мы пишем приложение, логику мы помещаем в класс App. Наше приложение использует сторонние данные, логику получение данных помещаем в класс Service. Получается класс App зависит от класса Service. Как это реализуется? Например так:
class Service
{
private $data;
private $key;
function __construct ($key = '123')
{
$this->key = $key;
}
function get()
{
return $data();
}
// ...
}
class App
{
protected $service;
function __construct()
{
$this->service = new Service();
}
function getData()
{
return $this->service->get();
}
// ...
}
$app = new App();
$app_data = $app->getData();
Всё хорошо работает пока нам не нужно больше гибкости. Что делать, если надо изменить $key сервиса? Необходимо менять код класса, например, "вшить" ключ внутрь конструктора App.
class App
{
protected $service;
function __construct()
{
$this->service = new Service('456');
}
// ...
}
Можно определить где-то хранилище настроек, или объявить константу за пределами класса App, или добавить ключ как параметр конструктора App.
class App
{
protected $service;
function __construct($key)
{
$this->service = new Service($key);
}
// ...
}
$app = new App('789');
Всё рассмотренные альтернативы являются bad practice.
- Внутрь класса App попадает вещи, которые к нему напрямую не относятся.
- Для тестирования нам необходимо вместо реального сервиса ставить заглушку, и возникает необходимость менять код класса App.
И вот на сцену выходит внедрение зависимостей. Вместо того, чтобы создавать Service объект внутри App класса, надо создать Service заранее и затем передать его в объект App как аргумент конструктора.
class App
{
protected $service;
function __construct(Service $service)
{
$this->service = $service;
}
// ...
}
$service = new Service('777');
$app = new App($service);
Всё. В этом вся суть внедрения зависимостей. Теперь можно настраивать сервис независимо от приложения, и легко подменить класс сервиса для тестирования.
Внедрение зависимостей не ограничивается инъекцией конструктора. Еще есть внедрение через setter метод и через свойство класса.
- Внедрение через конструктор:
class App
{
function __construct($service)
{
$this->service = $service;
}
// ...
}
- Внедрение через setter метод:
class App
{
function setService($service)
{
$this->service = $service;
}
// ...
}
- Внедрение через свойство класса:
class App
{
public $service;
}
$app->service = $service;
Как правило, внедрение через конструктор, как в нашем примере – это лучший способ для подключения основных зависимостей, а вот внедрение через setter – для добавления дополнительных зависимостей, например таких, как кэш.
Большинство современных PHP фреймворков активно используют внедрение зависимостей.
Что тако сервис-контейнер (Service Container) ?
Следующая вещь, о которой хочется поговорить - контейнер внедрения зависимостей Injection Dependency Container. По-русски обычно назыается сервис-контейнер (Service Container или IoC Container или Injector) - объект, который знает, как создавать и настраивать объекты. Чтобы выполнять свою работу, он должен знать об аргументах конструктора и отношениях между объектами.
В простейшем случае использовать класс контейнера достаточно просто:
class Container
{
public function getSomeService()
{
return new Service('option1','option2');
}
}
$container = new Container();
$service = $container->getSomeService();
В случае чуть сложнее мы сделаем контейнер настраиваемым, пробросив параметры option1 и option2 через конструктор контейнера.
class Container
{
protected $parameters = array();
public function __construct(array $parameters = array())
{
$this->parameters = $parameters;
}
public function getSomeService()
{
return new Service(
$this->parameters['option1'],
$this->parameters['option2']
);
}
}
$container = new Container(array(
'option1' => 'foo',
'option2' => 'bar'
);
$service = $container->getSomeService();
В случае еще чуть сложнее реализуется синглтон, чтобы getSomeService() возвращал всегда один и тот же экземпляр.
Автоматическое внедрение зависимостей
Опять же стоит упомянуть, что в современных фреймворках реализовано автоматическое внедрений зависимостей. Принципиально это работает следующим образом:
- Представим, что сервис-контейнер - это рюкзак.
- Внедряемые классы - вещи, лежащие в рюкзаке.
- У вас есть возможность класть вещи в рюкзак, при этом вы подписываете название вещи (класса) и краткую инструкцию как ей пользоваться (параметры конструктора класса и зависимости).
- Далее Вы указываете зависимости в своих классах
class A {
private $b;
private $c;
public function __construct(thingB $dependencyB, thingC: $dependencyC) // вещи thingB и thingC
{
$this->b = $dependencyB;
$this->c = $dependencyC;
}
// ...
}
// где-то в недрах фреймворка
$container = new Container;
$a = $container->get('A');
// или вот так в Laravel
$foo = App::make('A');
// или вот так в Angular
injector = ReflectiveInjector.resolveAndCreate([A, thingB, thingC]);
let a = injector.get(A);
- Когда создается экземпляр класса А, фреймворк берет рюкзак и начинает перебирать вещи, пытаясь найти на них названия thingB и thingC.
- Если находит, то создает экземпляры thingB и thingC используя краткую инструкцию. Иначе выбрасывает исключения.
- Передает созданные экземпляры dthingB и thingC в конструктор класса А и нужный нам объект создается. Надо заметить, что помещать класс А в контейнер необязательно, сервис-контейнер фреймворка используя механизм рефлексии PHP сам может получить параметры конструктора и исползовать эту информацию чтобы создать экземпляр класса А.