Рубрики
Без рубрики

Принцип Инверсии зависимостей

. Помечено как solid, java, ruby, дизайн программного обеспечения.

Последнее ТВЕРДОЕ правило – это принцип инверсии зависимостей. Согласно книге Роберта Мартина “Гибкая разработка программного обеспечения: принципы, шаблоны и практики”, принцип определяется как,

1) Модули высокого уровня не должны зависеть от модулей низкого уровня. И то, и другое должно зависеть от абстракций.

2) Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракции

Это очень много, поэтому давайте рассмотрим каждое правило отдельно.

Модули высокого уровня не должны зависеть от модулей низкого уровня

Давайте рассмотрим очень простой пример Java:

В этом примере Монитор зависит от кабеля DisplayPort и Кабель DisplayPort зависит от ноутбука . Если Монитор хочет использовать другой шнур, скажем, HDMI или VGA шнур, мы будем вынуждены сменить Монитор непосредственно. Мы могли бы предоставить Monitor несколько конструкторов для каждого типа карты; мы также могли бы создать базовый класс, который содержит поведение для всех шнуров. Однако эти подходы нежелательны, поскольку они делают класс жестким и громоздким для расширения, и он может стать раздутым. Базовый класс может показаться хорошей идеей, но слишком заманчиво добавлять поведение для всех шнуров в одном месте и просто распространять его на вновь созданные классы. Это раздувание имеет тенденцию нарушать принцип разделения интерфейса . То же самое относится и к взаимосвязи между Кабелем DisplayPort и Ноутбук .

В статически типизированных языках, таких как Java, лучше всего инвертировать зависимость с помощью интерфейса (или виртуальных классов в C++). Интерфейсы позволяют нам определять низкоуровневый тип для использования нашими классами более высокого уровня (например, Monitor ).

Инвертирование зависимости

При инвертировании зависимости нам нужны классы, от которых зависит (в нашем примере DisplayPort Cable и Ноутбук ), чтобы вместо этого зависеть от интерфейса. Продолжая наш пример, взаимосвязь между Кабелем DisplayPort и Монитор выглядит так:

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

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

Абстракции Не должны зависеть от Деталей. Детали Должны зависеть от Абстракции

Когда я думаю о деталях класса, мне нравится думать, что детали означают поведение и свойства класса. В нашем примере это означает/| #подключиться() и #Подключенное устройство . Хотя наш кабель DisplayPort абстрагирован, он по-прежнему зависит от ноутбука . Вопрос, который мы задаем, тот же самый, который мы задавали раньше. Что делать, если Видеокабель хотел подключиться к Рабочий стол или/| Смартфон вместо ноутбука ?

Итак, мы инвертируем зависимость, как делали раньше.

Динамический и статический подходы к инверсии зависимостей

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

Утиная типизация – это идея о том, что если поведение класса происходит определенным образом и разговаривает определенным образом, этот способ может быть абстрагирован от зависимых классов.

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

Проблема становится все более очевидной по мере того, как мы добавляем больше классов.

Поскольку определенного контракта не существует, мы могли бы использовать любой старый интерфейс с нашими новыми классами и просто запросить тип videoConnector и использовать любой интерфейс, который был определен. Однако это не только затрудняет расширение Monitor (поскольку нам пришлось бы вручную добавлять новое условие, чтобы понять новый тип), но и приводит к тому, что Monitor узнает о внутренней работе других классов, что напрямую нарушает этот принцип. Monitor должен зависеть от абстракции объекта, который он получает. Monitor не должен заботиться о классе объекта, только о его поведении.

Чтобы сделать это, мы должны сознательно убедиться, что классы имеют согласованный интерфейс и что они зависят от этого интерфейса. В случае нашего примера мы должны указать, будет ли #connectToDevice() ожидать видеоразъем , чтобы иметь #connect() или #connectToSomething() поведение.

в заключение

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

Оригинал: “https://dev.to/naomidennis/dependency-inversion-principle-1kbj”