Принцип инверсии зависимостей - Dependency inversion principle

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

  1. Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций (например, интерфейсов).
  2. Абстракции не должны зависеть от деталей. Детали (конкретные реализации) должны зависеть от абстракций.

Продиктовав это обе высокоуровневые и низкоуровневые объекты должны зависеть от одной и той же абстракции, этот принцип проектирования переворачивает как некоторые люди думают об объектно-ориентированном программировании.[2]

Идея, лежащая в основе пунктов A и B этого принципа, заключается в том, что при разработке взаимодействия между модулем высокого уровня и модулем низкого уровня взаимодействие следует рассматривать как абстрактное взаимодействие между ними. Это влияет не только на дизайн высокоуровневого модуля, но и на низкоуровневый: низкоуровневый модуль должен разрабатываться с учетом взаимодействия, и может потребоваться изменить его интерфейс использования.

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

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

Традиционный образец слоев

В традиционной архитектуре приложения компоненты нижнего уровня (например, уровень служебных программ) предназначены для использования компонентами более высокого уровня (например, уровень политики), которые позволяют создавать все более сложные системы. В этой композиции компоненты более высокого уровня напрямую зависят от компонентов более низкого уровня для выполнения некоторой задачи. Эта зависимость от компонентов более низкого уровня ограничивает возможности повторного использования компонентов более высокого уровня.[1]

Традиционные слои Pattern.png

Цель шаблона инверсии зависимостей - избежать этого сильно связанного распределения с посредничеством абстрактного уровня и увеличить возможность повторного использования более высоких уровней / уровней политики.

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

С добавлением абстрактного уровня уровни как верхнего, так и нижнего уровня уменьшают традиционные зависимости сверху вниз. Тем не менее, концепция «инверсии» не означает, что уровни нижнего уровня зависят от слоев более высокого уровня. Оба уровня должны зависеть от абстракций, которые рисуют поведение, необходимое для уровней более высокого уровня.

DIPLayersPattern.png

При прямом применении инверсии зависимостей абстракты принадлежат верхним уровням / уровням политики. Эта архитектура группирует компоненты более высокого уровня / политики и абстракции, определяющие более низкие службы, вместе в одном пакете. Уровни нижнего уровня создаются путем наследования / реализации этих абстрактные классы или интерфейсы.[1]

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

Обобщение паттерна инверсии зависимостей

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

  1. Проще рассматривать принцип хорошего мышления как образец кодирования. После того, как абстрактный класс или интерфейс были закодированы, программист может сказать: «Я выполнил работу по абстракции».
  2. Потому что многие модульное тестирование инструменты полагаются на наследование для достижения насмешливый, использование общих интерфейсов между классами (а не только между модулями, когда имеет смысл использовать универсальность) стало правилом.

Если используемый инструмент имитации полагается только на наследование, может возникнуть необходимость в широком применении шаблона инверсии зависимостей. У этого есть серьезные недостатки:

  1. Простая реализация интерфейса над классом недостаточна для уменьшения связи; только размышления о потенциальной абстракции взаимодействий могут привести к менее связанному дизайну.
  2. Внедрение универсальных интерфейсов повсюду в проекте затрудняет понимание и поддержку. На каждом этапе читатель будет спрашивать себя, каковы другие реализации этого интерфейса, и обычно ответ будет: только имитаторы.
  3. Обобщение интерфейса требует большего количества встроенного кода, в частности, фабрик, которые обычно полагаются на структуру внедрения зависимостей.
  4. Обобщение интерфейса также ограничивает использование языка программирования.

Ограничения обобщения

Наличие интерфейсов для выполнения шаблона инверсии зависимостей (DIP) имеет другие последствия для проектирования в объектно-ориентированная программа:

  • Все переменные-члены в классе должны быть интерфейсами или абстрактами.
  • Все пакеты конкретных классов должны подключаться только через интерфейс или пакеты абстрактных классов.
  • Никакой класс не должен происходить от конкретного класса.
  • Никакой метод не должен заменять реализованный метод.[1]
  • Для создания всех переменных требуется реализация творческий образец такой как заводской метод или фабрика узор, или использование внедрение зависимости рамки.

Ограничения имитации интерфейса

Использование инструментов имитации на основе наследования также вводит ограничения:

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

Будущие направления

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

  • Языки программирования будут продолжать развиваться, чтобы позволить им обеспечивать более строгие и точные контракты на использование, по крайней мере, в двух направлениях: обеспечение условий использования (предварительные, пост- и инвариантные условия) и интерфейсы на основе состояний. Это, вероятно, будет стимулировать и потенциально упростить более сильное применение шаблона инверсии зависимостей во многих ситуациях.
  • Все больше и больше инструментов для имитации теперь используют внедрение зависимости для решения проблемы замены статических и не виртуальных членов. Языки программирования, вероятно, будут развиваться для создания байт-кода, совместимого с имитацией. Одним из направлений будет ограничение использования не виртуальных участников. Другой - сгенерировать, по крайней мере, в тестовых ситуациях, байт-код, позволяющий имитировать ненаследование.

Реализации

Две распространенные реализации DIP используют аналогичную логическую архитектуру, но с разными последствиями.

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

Зависимость инверсия.png

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

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

Более гибкое решение извлекает абстрактные компоненты в независимый набор пакетов / библиотек:

DIPLayersPattern v2.png

Разделение каждого уровня на собственный пакет способствует повторному использованию любого уровня, обеспечивая надежность и мобильность.[1]

Примеры

Генеалогический модуль

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

Но для некоторых модулей более высокого уровня может потребоваться более простой способ просмотра системы: у любого человека могут быть дети, родители, братья и сестры (включая сводных братьев и сестер или нет), бабушек и дедушек, двоюродных братьев и т. Д.

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

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

В этом примере абстрагирование взаимодействия между модулями приводит к упрощенному интерфейсу модуля нижнего уровня и может привести к его более простой реализации.

Клиент удаленного файлового сервера

Представьте, что вам нужно подключить клиента к удаленному файловому серверу (FTP, облачное хранилище ...). Вы можете думать об этом как о наборе абстрактных интерфейсов:

  1. Подключение / Отключение (может потребоваться уровень сохранения соединения)
  2. Интерфейс создания / переименования / удаления / создания папок / тегов / списка
  3. Создание / замена / переименование / удаление / чтение файлов
  4. Поиск файлов
  5. Параллельная замена или удаление разрешения
  6. Управление историей файлов ...

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

Локальный диск обычно использует папку, удаленное хранилище может использовать папку и / или теги. Вы должны решить, как их объединить, если это возможно.

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

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

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

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

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

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

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

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

Контроллер представления модели

Пример DIP

Пакеты UI и ApplicationLayer содержат в основном конкретные классы. Контроллеры содержат аннотации / типы интерфейсов. UI имеет экземпляр ICustomerHandler. Все пакеты физически разделены. В ApplicationLayer есть конкретная реализация, которую будет использовать класс Page. Экземпляры этого интерфейса динамически создаются Factory (возможно, в том же пакете Controllers). Конкретные типы Page и CustomerHandler не зависят друг от друга; оба зависят от ICustomerHandler.

Прямой эффект заключается в том, что пользовательскому интерфейсу не нужно ссылаться на ApplicationLayer или какой-либо конкретный пакет, реализующий ICustomerHandler. Конкретный класс будет загружен с использованием отражения. В любой момент конкретная реализация может быть заменена другой конкретной реализацией без изменения класса UI. Другая интересная возможность заключается в том, что класс Page реализует интерфейс IPageViewer, который можно передать в качестве аргумента методам ICustomerHandler. Тогда конкретная реализация сможет взаимодействовать с пользовательским интерфейсом без конкретной зависимости. Опять же, оба связаны интерфейсами.

Связанные шаблоны

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

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

История

Принцип инверсии зависимости был постулирован Роберт С. Мартин и описан в нескольких публикациях, в том числе в статье Метрики качества объектно-ориентированного дизайна: анализ зависимостей,[3] статья, опубликованная в отчете C ++ в мае 1996 г., озаглавленная Принцип инверсии зависимостей,[4] и книги Гибкая разработка программного обеспечения, принципы, шаблоны и практики,[1] и Принципы, шаблоны и практики Agile в C #.

Смотрите также

Рекомендации

  1. ^ а б c d е ж Мартин, Роберт С. (2003). Гибкая разработка программного обеспечения, принципы, шаблоны и практики. Прентис Холл. С. 127–131. ISBN  978-0135974445.
  2. ^ Фриман, Эрик; Фриман, Элизабет; Кэти, Сьерра; Берт, Бейтс (2004). Хендриксон, Майк; Лукидес, Майк (ред.). Шаблоны проектирования Head First (мягкая обложка). 1. О'РЕЙЛИ. ISBN  978-0-596-00712-6. Получено 2012-06-21.
  3. ^ Мартин, Роберт К. (октябрь 1994 г.). «Метрики качества объектно-ориентированного дизайна: анализ зависимостей» (PDF). Получено 2016-10-15.
  4. ^ Мартин, Роберт К. (май 1996 г.). «Принцип инверсии зависимостей» (PDF). Отчет C ++. Архивировано из оригинал (PDF) на 2011-07-14.

внешняя ссылка