Внедрение зависимости

08.08.2021

Внедрение зависимости (англ. Dependency injection, DI) — процесс предоставления внешней зависимости программному компоненту. Является специфичной формой «инверсии управления» (англ. Inversion of control, IoC), когда она применяется к управлению зависимостями. В полном соответствии с принципом единственной обязанности объект отдаёт заботу о построении требуемых ему зависимостей внешнему, специально предназначенному для этого общему механизму.

Настоящее внедрение зависимости

При использовании паттерна «внедрение зависимости» объект пассивен и не предпринимает вообще никаких шагов для выяснения зависимостей, а предоставляет для этого сеттеры и/или принимает своим конструктором аргументы, посредством которых внедряются зависимости.

Принцип работы

Работа фреймворка, обеспечивающая внедрение зависимости, описывается следующим образом. Приложение, независимо от оформления, исполняется внутри контейнера IoC, предоставляемого фреймворком. Часть объектов в программе по-прежнему создается обычным способом языка программирования, часть создается контейнером на основе предоставленной ему конфигурации.

Условно, если объекту нужно получить доступ к определенному сервису, объект берет на себя обязанность по доступу к этому сервису: он или получает прямую ссылку на местонахождение сервиса, или обращается к известному «сервис-локатору» и запрашивает ссылку на реализацию определенного типа сервиса. Используя же внедрение зависимости, объект просто предоставляет свойство, которое в состоянии хранить ссылку на нужный тип сервиса; и когда объект создается, ссылка на реализацию нужного типа сервиса автоматически вставляется в это свойство (поле), используя средства среды.

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

С другой стороны, излишнее использование внедрения зависимостей может сделать приложения более сложными и трудными в сопровождении: так как для понимания поведения программы программисту необходимо смотреть не только в исходный код, а еще и в конфигурацию, а конфигурация, как правило, невидима для IDE, которые поддерживают анализ ссылок и рефакторинг, если явно не указана поддержка фреймворков с внедрениями зависимостей.

Примеры кода

При использовании внедрения зависимостей, как правило, существует конфигурационный механизм или архитектура, которая определяет целесообразность выбора той или иной реализации в зависимости от поставленных целей.

Пример кода на PHP

<?php /** * Класс конфигурация базы данных */ class DbConfiguration { private $host; private $port; private $username; private $password; public function __construct(string $host, int $port, string $username, string $password) { // вся суть Di находится в строчках ниже $this->host = $host; $this->port = $port; $this->username = $username; $this->password = $password; } public function getHost() { return $this->host; } public function getPort() { return $this->port; } public function getUsername() { return $this->username; } public function getPassword() { return $this->password; } } /** * Класс соединение с базой данных */ class DbConnection { private $configuration; public function __construct(DbConfiguration $config) { // вся суть Di находится в строчке ниже $this->configuration = $config; } public function getDsn() { // примечание: это не реальный dsn, разделители в реальном dsn другие return sprintf( '%s:%s@%s:%d', $this->configuration->getUsername(), $this->configuration->getPassword(), $this->configuration->getHost(), $this->configuration->getPort() ); } } // создаем объект конфигурации базы данных, передавая в конструктор параметры $config = new DbConfiguration('localhost', 3306, 'username', 'password'); // создаем объект соединения с базой, посылая в конструктор объект конфигурации // использование Di дает слабосвязность кода $connection = new DbConnection($config);

Пример кода на Java

public interface ICar { public float getSpeed(); public void setPedalPressure(final float PEDAL_PRESSURE); } public interface IEngine { public float getEngineRotation(); public void setFuelConsumptionRate(final float FUEL_FLOW); }

Без использования dependency injection

public class DefaultEngineImpl implements IEngine { private float engineRotation = 0; public float getEngineRotation() { return engineRotation; } public void setFuelConsumptionRate(final float FUEL_FLOW) { engineRotation = …; } } public class DefaultCarImpl implements ICar { private IEngine engine = new DefaultEngineImpl(); public float getSpeed() { return engine.getEngineRotation()*…; } public void setPedalPressure(final float PEDAL_PRESSURE) { engine.setFuelConsumptionRate(…); } } public class MyApplication { public static void main(String[] args) { DefaultCarImpl car = new DefaultCarImpl(); car.setPedalPressure(5); float speed = car.getSpeed(); System.out.println("Speed of the car is " + speed); } }

Внедрение зависимости вручную

public class DefaultCarImpl implements ICar { private IEngine engine; public DefaultCarImpl(final IEngine engineImpl) { engine = engineImpl; } public float getSpeed() { return engine.getEngineRotation()*…; } public void setPedalPressure(final float PEDAL_PRESSURE) { engine.setFuelConsumptionRate(…); } } public class CarFactory { public static ICar buildCar() { return new DefaultCarImpl(new DefaultEngineImpl()); } } public class MyApplication { public static void main(String[] args) { ICar car = CarFactory.buildCar(); car.setPedalPressure(5); float speed = car.getSpeed(); System.out.println("Speed of the car is " + speed); } }

Внедрение зависимости при помощи фреймворка

<service-point id="CarBuilderService"> <invoke-factory> <construct class="Car"> <service>DefaultCarImpl</service> <service>DefaultEngineImpl</service> </construct> </invoke-factory> </service-point> /** Неявная реализация **/ public class MyApplication { public static void main(String[] args) { Service service = (Service)DependencyManager.get("CarBuilderService"); ICar car = (ICar)service.getService(Car.class); car.setPedalPressure(5); float speed = car.getSpeed(); System.out.println("Speed of the car is " + speed); } }

Имя:*
E-Mail:
Комментарий: