В чем разница между инверсией зависимости и шаблоном отдельного интерфейса (или кодом для взаимодействия в целом)?
Я не могу понять разницу между принципом обращения зависимостей (одним из принципов SOLID) и общим шаблоном "Код для интерфейсов" или "Отдельный интерфейс". Все они выступают за создание уровня абстракции для отделения модулей более низкого уровня и более высокого уровня.
Принцип DI предусматривает создание интерфейсов для взаимодействия между модулями более высокого и более низкого уровня, но также настаивает на том, что интерфейсы должны быть частью пакета более высокого уровня.
Почему это должно быть частью более высокого уровня, а не более низкого уровня? Это нижний уровень, который демонстрирует его поведение, поэтому разве развязывающие интерфейсы не должны быть частью более низкого уровня? Что если есть несколько модулей более высокого уровня, зависящих от одного и того же более низкого уровня?
Или еще,
Почему бы не сделать отдельный пакет, в который помещаются все интерфейсы, которые затем могут использоваться как на более высоком, так и на более низком уровне? (Это предусмотрено шаблоном Separated Interfaces.)
Моя дилемма в том, что я не могу определить относительное использование и преимущества или каждого из них.
Пожалуйста, не цитируйте статью Дерека Грира или Роберта Мартина. Я прочитал эти статьи, но путаница все еще сохраняется.
3 ответа
Инверсия зависимости - это концепция, которая является строительным блоком для структур внедрения зависимости, таких как Spring, Guice и т. Д. Он говорит о том, что компонент не должен искать свою зависимость, а должен вводить свою зависимость извне. Зависимость для компонента может быть интерфейсом или другим классом. Когда зависимость является интерфейсом, компонент может работать с различными реализациями.
Давайте рассмотрим пример. Допустим, кто-то пишет компонент, которому необходимо выполнить некоторые вызовы службы. Если разработчик компонента считает, что вызовы службы должны всегда выполняться с использованием HTTP, он сделает HTTP-клиент зависимым. Теперь это ограничивает утилиту компонентов возможностью вызова только HTTP-сервисов. Однако тот же разработчик мог бы создать интерфейс, скажем, GenericExternalServiceInvoker, и реализовать две реализации - одну с использованием HTTP, а другую с использованием какой-либо системы массового обслуживания, такой как ActiveMQ. Использование интерфейса позволило разработчику компонентов создавать компоненты, которые полезны в гораздо более широком смысле.
В приведенном выше примере мы предположили, что DI был на месте. Потому что зависимость вводилась извне. Разработчик компонента мог бы сделать другой выбор дизайна и мог бы создать экземпляр HTTP Client в коде, и, таким образом, пользователям компонента было бы трудно изменить его поведение, если возникнет такая необходимость, для использования чего-то другого, кроме HTTP.
Итак, в итоге, следует использовать DI, чтобы компонент не зависел от его зависимости в аппаратной форме. Делая зависимость компонента интерфейсом, в компонент встроена дополнительная гибкость. Второй принцип, Код для интерфейсов, является для разработчика направляющим фактором при выборе интерфейса в качестве зависимости, а не конкретного класса.
Я полагаю, вы имели в виду разницу между внедрением зависимости и программированием для интерфейсов.
Программирование на интерфейсах
Программирование на интерфейсах - это практика в разработке программного обеспечения, когда различные объекты взаимодействуют друг с другом через общедоступные интерфейсы. Это обеспечивает большую гибкость, поскольку можно легко переключаться с одной реализации интерфейса на другую с минимальными изменениями в кодовой базе и даже может избежать перекомпиляции кода.
Внедрение зависимости
Внедрение зависимостей - это механизм, позволяющий создавать экземпляры объектов скорее архитектуре, чем разработчику. В большинстве случаев объекту может потребоваться, чтобы некоторые дополнительные объекты (так называемые зависимости) уже существовали и передавались в конструктор текущего объекта или в свойство этого объекта, чтобы его можно было использовать. Внедрение зависимостей - это механизм, который позволяет инфраструктуре предоставлять и устанавливать их при создании требуемого объекта (внедрение их). Обычно фреймворк знает, что делать, основываясь на внешней конфигурации или проверке кода.
Парадигма внедрения зависимостей опирается на практику программирования с интерфейсами (именно поэтому я и описал ее вначале), поскольку зависимости обычно предоставляются через их открытые интерфейсы. Таким образом, различные реализации могут использоваться только на основании изменений конфигурации. Это позволило бы легко использовать фиктивную настройку объектов при тестировании определенных аспектов приложения.
Обычно внедрение зависимостей относится к инверсии механизма управления, так как это заставляет каркас (то есть код приложения) иметь дело с созданием объекта, а не с созданием вручную (выполняется разработчиком).
согласно комментарию aknon:
Является ли внедрение зависимостей таким же, как инверсия зависимостей?
Не обязательно Инверсию зависимостей можно также назвать процессом рефакторинга инвертирования зависимостей между двумя объектами. Например class A
может зависеть от class B
до рефакторинга, и после этого вы можете иметь class B
зависит от class A
вместо этого, таким образом инвертируя их зависимости. Внедрение зависимостей - это механизм, обеспечивающий зависимости без явного написания кода.
Обновить
Для получения дополнительной информации о термине инверсия зависимостей, я рекомендую вам прочитать эту статью от Rich Newman. Он является частью серии статей, посвященных технологии Microsoft Smart Client Software Factory (в настоящее время устаревшей), но статья достаточно нейтральна и может выдержать само по себе.
Смотрите также:
Программирование интерфейсных интерфейсов обычно требуется для внедрения сложных зависимостей. Я не думаю, что размещение API в отдельном модуле, или просто в другом пакете, или в параллельной реализации может быть в принципе правильным или неправильным. Это зависит: большинство проектов, которые я видел, объединяли интерфейсы и реализации вместе, но бывают случаи, когда двоичные файлы API даже отправляются в другом архиве, например servlet-api.jar
или же logging-api.jar
,
На самом деле я не вижу смысла сравнивать двойки на такой детали. Через некоторое время я решил, что здравый смысл - это единственный принцип, который всегда применяется, и при чтении методологии / шаблона / того, что следует учитывать, автор продает книги, выступает на конференциях и в целом делает из этого бизнес. Нельзя сказать, что эти чтения бесполезны, скорее наоборот: они являются фундаментальным способом учиться на успехах и неудачах других. Но аргументы должны быть приняты с недоверием, потому что, возможно, решение не вписывается в конкретный проект.
Единственная "библия" в этой области - это вещи, которые можно как-то измерить, такие как структуры данных и алгоритмы, а также спецификации. Я бы не стал беспокоиться о том, кто более прав между дядей Бобом и Мартином Фаулером в отношении того, где разместить ваши интерфейсы: размещайте его там, где вам удобно, и там, где это работает для вас. Через некоторое время, если вы приняли неправильное решение, вы всегда сможете провести рефакторинг и изменить ситуацию.