Принцип инверсии зависимостей
![]() | Эта статья включает список общих ссылок , но в ней отсутствуют достаточные соответствующие встроенные цитаты . ( сентябрь 2018 г. ) |
Эта статья нуждается в дополнительных цитатах для проверки . ( сентябрь 2018 г. ) |
ТВЕРДЫЙ |
---|
Принципы |
В объектно-ориентированном проектировании принцип инверсии зависимостей — это специфическая методология для слабосвязанных программных модулей . При следовании этому принципу традиционные отношения зависимости , установленные от модулей высокого уровня, определяющих политику, к модулям зависимостей низкого уровня, меняются местами, таким образом, делая модули высокого уровня независимыми от деталей реализации модуля низкого уровня. Принцип гласит: [1]
- Модули высокого уровня не должны ничего импортировать из модулей низкого уровня. Оба должны зависеть от абстракций (например, интерфейсов).
- Абстракции не должны зависеть от деталей. Детали (конкретные реализации) должны зависеть от абстракций.
Предписывая, что как высокоуровневые, так и низкоуровневые объекты должны зависеть от одной и той же абстракции, этот принцип проектирования переворачивает представление некоторых людей об объектно-ориентированном программировании. [2]
Идея пунктов А и Б этого принципа заключается в том, что при проектировании взаимодействия между модулем высокого уровня и модулем низкого уровня взаимодействие следует рассматривать как абстрактное взаимодействие между ними. Это имеет последствия не только для проектирования модуля высокого уровня, но и для модуля низкого уровня: модуль низкого уровня должен быть спроектирован с учетом взаимодействия, и может потребоваться изменить его интерфейс использования.
Во многих случаях рассмотрение взаимодействия само по себе как абстрактной концепции позволяет уменьшить связанность компонентов без введения дополнительных шаблонов кодирования, позволяя использовать только более легкую и менее зависимую от реализации схему взаимодействия.
Когда обнаруженная абстрактная схема взаимодействия между двумя модулями является общей и обобщение имеет смысл, этот принцип проектирования также приводит к следующему шаблону кодирования инверсии зависимостей.
Традиционный узор слоев
[ редактировать ]В традиционной архитектуре приложений компоненты более низкого уровня (например, служебный уровень) предназначены для использования компонентами более высокого уровня (например, уровень политики), которые позволяют создавать все более сложные системы. В этой композиции компоненты более высокого уровня напрямую зависят от компонентов более низкого уровня для достижения некоторой задачи. Эта зависимость от компонентов более низкого уровня ограничивает возможности повторного использования компонентов более высокого уровня. [1]

Цель шаблона инверсии зависимостей — избежать этого сильно связанного распределения с помощью абстрактного уровня и повысить возможность повторного использования более высоких уровней/уровней политики.
Шаблон инверсии зависимостей
[ редактировать ]С добавлением абстрактного уровня уровни как высокого, так и нижнего уровня уменьшают традиционные зависимости сверху вниз. Тем не менее, концепция «инверсии» не означает, что уровни более низкого уровня напрямую зависят от слоев более высокого уровня . Оба уровня должны зависеть от абстракций (интерфейсов), которые предоставляют поведение, необходимое слоям более высокого уровня.

При прямом применении инверсии зависимостей абстрактные данные принадлежат верхним уровням/уровням политики. Эта архитектура группирует компоненты высшего уровня/политики и абстракции, которые определяют сервисы низшего уровня, в одном пакете. Уровни нижнего уровня создаются путем наследования/реализации этих абстрактных классов или интерфейсов. [1]
Инверсия зависимостей и владения способствует повторному использованию более высоких уровней/уровней политики. Верхние уровни могут использовать другие реализации нижних сервисов. Когда компоненты нижнего уровня закрыты или когда приложению требуется повторное использование существующих сервисов, обычно адаптер выступает в роли посредника между сервисами и абстракциями.
Обобщение шаблона инверсии зависимостей
[ редактировать ]Во многих проектах принцип и шаблон инверсии зависимостей рассматриваются как единая концепция, которую следует обобщить, т. е. применить ко всем интерфейсам между программными модулями. На это есть как минимум две причины:
- Принцип хорошего мышления проще рассматривать как шаблон кодирования. После того как абстрактный класс или интерфейс написан, программист может сказать: «Я выполнил работу по абстракции».
- Поскольку многие инструменты модульного тестирования полагаются на наследование для выполнения макетирования , использование универсальных интерфейсов между классами (а не только между модулями, когда имеет смысл использовать общность) стало правилом.
Если используемый инструмент макетирования опирается только на наследование, может возникнуть необходимость широко применять шаблон инверсии зависимостей. Это имеет серьезные недостатки:
- Простой реализации интерфейса поверх класса недостаточно для уменьшения связанности; только размышления о потенциальной абстракции взаимодействий могут привести к менее связанному дизайну.
- Внедрение универсальных интерфейсов повсюду в проекте усложняет его понимание и поддержку. На каждом этапе читатель будет спрашивать себя, каковы еще реализации этого интерфейса, и обычно отвечает: только издевательство.
- Обобщение интерфейса требует большего количества сантехнического кода, в частности фабрик, которые обычно полагаются на структуру внедрения зависимостей.
- Обобщение интерфейса также ограничивает использование языка программирования.
Ограничения обобщения
[ редактировать ]Наличие интерфейсов для реализации шаблона инверсии зависимостей (DIP) имеет и другие последствия для проектирования объектно-ориентированной программы :
- Все переменные-члены класса должны находиться в интерфейсах или абстракциях.
- Все пакеты конкретных классов должны подключаться только через пакеты интерфейсов или абстрактных классов.
- Ни один класс не должен быть производным от конкретного класса.
- Ни один метод не должен переопределять реализованный метод. [1]
- Любое создание экземпляров переменных требует реализации шаблона создания, такого как фабричный метод или фабричный шаблон, или использования инфраструктуры внедрения зависимостей .
Ограничения на макетирование интерфейса
[ редактировать ]Использование инструментов макетирования на основе наследования также накладывает ограничения:
- Статические, видимые извне члены должны систематически полагаться на внедрение зависимостей, что значительно усложняет их реализацию.
- Все тестируемые методы должны стать реализацией интерфейса или переопределением абстрактного определения.
Будущие направления
[ редактировать ]Принципы – это образ мышления. Шаблоны — это распространенные способы решения проблем. Могут существовать шаблоны кодирования, заменяющие недостающие функции языка программирования.
- Языки программирования будут продолжать развиваться, что позволит им обеспечивать более строгие и точные контракты использования, по крайней мере, в двух направлениях: обеспечение условий использования (пред-, пост- и инвариантные условия) и интерфейсов на основе состояний. Это, вероятно, будет стимулировать и потенциально упрощать более строгое применение шаблона инверсии зависимостей во многих ситуациях.
- Все больше и больше инструментов для насмешек теперь используют внедрение зависимостей для решения проблемы замены статических и невиртуальных членов. Языки программирования, вероятно, будут развиваться, чтобы генерировать байт-код, совместимый с макетами. Одним из направлений будет ограничение использования невиртуальных участников. Другой вариант — генерировать, по крайней мере, в тестовых ситуациях, байт-код, позволяющий создавать макеты без наследования.
Реализации
[ редактировать ]Две распространенные реализации DIP используют схожую логическую архитектуру, но с разными последствиями.
Прямая реализация объединяет классы политики с абстрактными классами служб в одной библиотеке. В этой реализации компоненты высокого уровня и компоненты низкого уровня распределяются в отдельные пакеты/библиотеки, где интерфейсы, определяющие поведение/сервисы, необходимые компоненту высокого уровня, принадлежат библиотеке компонента высокого уровня и существуют в ней. Реализация интерфейса компонента высокого уровня компонентом низкого уровня требует, чтобы пакет компонента низкого уровня зависел от компонента высокого уровня для компиляции, тем самым инвертируя традиционные отношения зависимости.

На рисунках 1 и 2 показан код с той же функциональностью, однако на рисунке 2 используется интерфейс для инвертирования зависимости. Направление зависимости можно выбрать, чтобы максимизировать повторное использование кода политики и устранить циклические зависимости.
В этой версии DIP зависимость компонентов нижнего уровня от интерфейсов/абстрактов на уровнях более высокого уровня затрудняет повторное использование компонентов нижнего уровня. Вместо этого эта реализация ″инвертирует″ традиционную зависимость сверху вниз в противоположную сторону – снизу вверх.
Более гибкое решение извлекает абстрактные компоненты в независимый набор пакетов/библиотек:

Разделение каждого уровня на отдельный пакет способствует повторному использованию любого уровня, обеспечивая надежность и мобильность. [1]
Примеры
[ редактировать ]Генеалогический модуль
[ редактировать ]Генеалогическая система может представлять отношения между людьми в виде графика прямых связей между ними (отец-сын, отец-дочь, мать-сын, мать-дочь, муж-жена, жена-муж и т. д.). Это очень эффективно и расширяемо, так как легко добавить бывшего мужа или законного опекуна.
Но некоторые модули более высокого уровня могут потребовать более простого способа просмотра системы: у любого человека могут быть дети, родители, братья и сестры (включая сводных братьев и сестер или нет), бабушки и дедушки, двоюродные братья и сестры и так далее.
В зависимости от использования генеалогического модуля представление общих связей как отдельных прямых свойств (скрытие графа) значительно облегчит связь между модулем более высокого уровня и генеалогическим модулем и позволит полностью изменить внутреннее представление прямых связей без каких-либо изменений. влияние на модули, использующие их. Это также позволяет встраивать точные определения братьев, сестер или дядей в генеалогический модуль, обеспечивая тем самым соблюдение принципа единой ответственности .
Наконец, если первый подход с расширяемыми обобщенными графами кажется наиболее расширяемым, использование генеалогического модуля может показать, что более специализированная и простая реализация отношений достаточна для приложения (приложений) и помогает создать более эффективную систему.
В этом примере абстрагирование взаимодействия между модулями приводит к упрощению интерфейса модуля нижнего уровня и может привести к более простой его реализации.
Клиент удаленного файлового сервера
[ редактировать ]Клиент удаленного файлового сервера (FTP, облачное хранилище...) можно смоделировать как набор абстрактных интерфейсов:
- Соединение/отключение (может потребоваться уровень сохранения соединения)
- Интерфейс создания/переименования/удаления/списка папок/тегов
- Интерфейс создания/замены/переименования/удаления/чтения файлов
- Поиск файлов
- Одновременная замена или удаление разрешения
- Управление историей файлов...
Если локальные и удаленные файлы предлагают одни и те же абстрактные интерфейсы, модули высокого уровня, реализующие шаблон инверсии зависимостей, могут использовать их без разбора. Приложение сможет прозрачно сохранять свои документы локально или удаленно.
Следует учитывать уровень обслуживания, требуемый модулями высокого уровня.
Проектирование модуля как набора абстрактных интерфейсов и адаптация к нему других модулей может обеспечить общий интерфейс для многих систем.
Модель-представление-контроллер
[ редактировать ]
Пакеты UI и ApplicationLayer содержат в основном конкретные классы. Контроллеры содержат абстрактные типы/типы интерфейсов. В пользовательском интерфейсе есть экземпляр ICustomerHandler. Все пакеты физически разделены. В ApplicationLayer имеется конкретная реализация CustomerHandler, которую будет использовать класс Page. Экземпляры интерфейса ICustomerHandler создаются фабрикой динамически (возможно, в том же пакете контроллеров). Конкретные типы Page и CustomerHandler зависят от ICustomerHandler, а не друг от друга.
Поскольку пользовательский интерфейс не ссылается на ApplicationLayer или какой-либо другой конкретный пакет, реализующий ICustomerHandler, конкретную реализацию CustomerHandler можно заменить без изменения класса пользовательского интерфейса. Кроме того, класс Page реализует интерфейс IPageViewer, который можно передавать в качестве аргумента методам ICustomerHandler, позволяя конкретной реализации CustomerHandler взаимодействовать с пользовательским интерфейсом без конкретной зависимости. Опять же, оба связаны интерфейсами.
Связанные шаблоны
[ редактировать ]Применение принципа инверсии зависимостей также можно рассматривать как пример шаблона адаптера . То есть класс высокого уровня определяет свой собственный интерфейс адаптера, который является абстракцией, от которой зависят другие классы высокого уровня. Адаптированная реализация также обязательно зависит от той же абстракции интерфейса адаптера, хотя ее можно реализовать с помощью кода из собственного низкоуровневого модуля. Модуль высокого уровня не зависит от модуля низкого уровня, поскольку он использует функциональность низкого уровня только косвенно через интерфейс адаптера, вызывая к интерфейсу полиморфные методы, которые реализованы адаптированной реализацией и ее модулем низкого уровня. [ нужна ссылка ]
Различные шаблоны, такие как плагин , локатор сервисов , [3] или внедрение зависимостей [4] [5] используются для облегчения предоставления во время выполнения выбранной реализации компонента низкого уровня компоненту высокого уровня. [ нужна ссылка ]
История
[ редактировать ]Принцип инверсии зависимостей был постулирован Робертом К. Мартином и описан в нескольких публикациях, включая статью « Метрики качества объектно-ориентированного проектирования: анализ зависимостей» , [6] статья, опубликованная в C++ Report в июне 1996 года, озаглавленная «Принцип инверсии зависимостей» , [7] и книги «Гибкая разработка программного обеспечения, принципы, шаблоны и практики» , [1] и принципы, шаблоны и практики Agile в C# .
См. также
[ редактировать ]- Шаблон адаптера
- Внедрение зависимостей
- Проектирование по договору
- Интерфейс
- Парадокс изобретателя
- Инверсия управления
- Плагин (вычисления)
- Шаблон локатора сервисов
- SOLID – буква «D» в слове «SOLID» означает принцип инверсии зависимостей.
Ссылки
[ редактировать ]- ^ Jump up to: а б с д и ж Мартин, Роберт С. (2003). Гибкая разработка программного обеспечения, принципы, шаблоны и практики . Прентис Холл. стр. 127–131. ISBN 978-0135974445 .
- ^ Фриман, Эрик; Фриман, Элизабет; Кэти, Сьерра; Берт, Бейтс (2004). Хендриксон, Майк; Лукидес, Майк (ред.). Шаблоны проектирования Head First (мягкая обложка) . Том. 1. О'РЕЙЛИ. ISBN 978-0-596-00712-6 . Проверено 21 июня 2012 г.
- ^ Аунг, Нэй Лин (01 декабря 2018 г.). «Локатор сервисов против внедрения зависимостей» . Середина . Проверено 06 декабря 2022 г.
- ^ Мэтьюз, Саша (25 марта 2021 г.). «Вы просто внедряете зависимость, думая, что следуете инверсии зависимости…» . Середина . Проверено 06 декабря 2022 г.
- ^ Эрез, Гай (09 марта 2022 г.). «Инверсия зависимостей против внедрения зависимостей» . Середина . Проверено 06 декабря 2022 г.
- ^ Мартин, Роберт К. (октябрь 1994 г.). «Метрики качества объектно-ориентированного проектирования: анализ зависимостей» (PDF) . Проверено 15 октября 2016 г.
- ^ Мартин, Роберт С. (июнь 1996 г.). «Принцип инверсии зависимостей» (PDF) . Отчет С++ . Том. 8, нет. 6. С. 61–66. ISSN 1040-6042 . Архивировано из оригинала (PDF) 14 июля 2011 г.
Внешние ссылки
[ редактировать ]- Метрики качества объектно-ориентированного проектирования: анализ зависимостей Роберт К. Мартин, отчет C++, сентябрь/октябрь 1995 г.
- Принцип инверсии зависимостей, Роберт К. Мартин, отчет C++, май 1996 г.
- Исследование принципа инверсии зависимостей, Дерек Грир
- DIP: принцип инверсии зависимостей
- DIP в дикой природе, Бретт Л. Шухерт, май 2013 г.
- Принцип инверсии зависимостей
- «Маленькая архитектура», Роберт К. Мартин (дядя Боб), январь 2016 г. — блог о значении принципа инверсии зависимостей в архитектуре программного обеспечения.