Из Википедии, свободной энциклопедии
Перейти к навигации Перейти к поиску

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

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

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

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

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

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

Традиционный узор слоев [ править ]

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

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

Шаблон инверсии зависимости [ править ]

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

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

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

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

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

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

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

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

Ограничения обобщения [ править ]

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

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

Ограничения имитации интерфейса [ править ]

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

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

Будущие направления [ править ]

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

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

Реализации [ править ]

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

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

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

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

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

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

Примеры [ править ]

Генеалогический модуль [ править ]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Связанные шаблоны [ править ]

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

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

История [ править ]

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

См. Также [ править ]

  • Шаблон адаптера
  • Внедрение зависимости
  • Дизайн по контракту
  • Интерфейс
  • Инверсия контроля
  • Плагин (вычисления)
  • Шаблон локатора услуг
  • SOLID - буква "D" в "SOLID" обозначает принцип инверсии зависимостей.
  • Парадокс изобретателя

Ссылки [ править ]

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

Внешние ссылки [ править ]

  • Метрики качества объектно-ориентированного дизайна: анализ зависимостей Роберт К. Мартин, Отчет C ++, сентябрь / октябрь 1995 г.
  • Принцип инверсии зависимостей, Роберт К. Мартин, отчет C ++, май 1996 г.
  • Изучение принципа инверсии зависимостей, Дерек Грир
  • DIP in the Wild, Бретт Л. Шухерт, май 2013 г.
  • Контейнер IoC для Unity3D - часть 2
  • A Little Architecture, Роберт К. Мартин (дядя Боб), январь 2016 г. - блог о значении принципа инверсии зависимостей в архитектуре программного обеспечения.