Поиск по сайту:

Что такое внедрение зависимостей в программировании?


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

Основной пример

Термин «внедрение зависимостей» и его определение могут показаться сложными. На практике это простая концепция. Давайте рассмотрим два подхода к созданию базового объекта. Здесь мы используем PHP, но концепции применимы ко всем объектно-ориентированным кодовым базам.

Без внедрения зависимостей

class UserCreator {
 
    protected Mailer $Mailer;
 
    public function __construct() {
        $this -> Mailer = new Mailer();
    }
 
    public function create(string username, string $email) : void {
        $this -> Mailer -> send($email, "Your account", "Welcome.");
    }
 
}

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

class UserCreator {
 
    protected Mailer $Mailer;
 
    public function __construct(Mailer $mailer) {
        $this -> Mailer = $Mailer;
    }
 
    public function create(string $username, string $email) : void {
        $this -> Mailer -> send($email, "Your account", "Welcome.");
    }
 
}

Разница тонкая, но существенная. Оба класса имеют зависимость от экземпляра Mailer. Первый класс создает свой собственный Mailer, тогда как второй работает с Mailer, предоставленным внешним миром. Зависимость Mailer была внедрена в класс.

Преимущества внедрения зависимостей

Основным преимуществом внедрения зависимостей является разделение классов и их зависимостей, которое оно обеспечивает. Внедрение зависимостей — это форма инверсии управления: вместо того, чтобы классы контролировали свои собственные зависимости, они работают с экземплярами, предоставленными их внешней средой.

Говоря более конкретно, внедрение ваших зависимостей упрощает изменение этих зависимостей в будущем. В реальной кодовой базе Mailer в нашем примере выше, вероятно, будет интерфейсом с такими реализациями, как SendmailMailer, SendGridMailer и FakeMailer.

Взяв первый пример, когда классы напрямую создают один из экземпляров Mailer, это приводит к дополнительной работе, если вам затем нужно заменить эту реализацию Mailer. С внедрением зависимостей вы можете указать интерфейсу Mailer принять любую совместимую реализацию. Реализация для использования определяется внешним миром.

В большинстве случаев вы будете использовать внедрение зависимостей в сочетании с контейнером внедрения зависимостей. Контейнеры обычно интегрируются с используемой вами инфраструктурой приложения. Они автоматически разрешают и внедряют зависимости классов.

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

Влияние на тестирование

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

class FakeMailer implements Mailer {
    public function send(string $to, string $subject, string $body) : void {
        return;
    }
}

Нам не нужно отправлять приветственное письмо при тестировании нашего UserCreator. Тем не менее, это было бы неизбежно в первом примере, где UserCreator всегда создает свой собственный активный Mailer. С внедрением зависимостей мы можем предоставить специальный Mailer, который удовлетворяет контракту интерфейса, но устраняет побочные эффекты.

Слабая связь

Внедрение зависимостей не означает полное устранение зависимостей — они всегда будут, но они должны быть слабо связаны. Думайте о зависимости как о чем-то, что прикреплено на определенный период времени, а не о приспособлении, которое приклеено навсегда.

Тесная связь, как видно из первого примера, делает негибкий код, который трудно перемещать. Зависимости становятся непрозрачными для внешних наблюдателей, таких как исполнители тестов. Интерфейсы подсказки типов, переданные в ваш класс, сохраняют ваши зависимости настолько слабо связанными, насколько это возможно.

Принцип единой ответственности

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

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

Заключение

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

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

Реализация Mailer также является деталью конфигурации. UserCreator не должен заниматься настройкой почтовой системы. Скорее всего, вы захотите повторно использовать ту же систему в другом месте своей кодовой базы. Конфигурация меняется чаще, чем код; вы, вероятно, захотите изменить способ отправки почты задолго до того, как пересмотрите бизнес-требование о том, что электронная почта отправляется при регистрации пользователя.

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




Все права защищены. © Linux-Console.net • 2019-2024