Когда НЕ применять принцип инверсии зависимостей?

В настоящее время я пытаюсь выяснить SOLID. Таким образом, принцип инверсии зависимостей означает, что любые два класса должны взаимодействовать через интерфейсы, а не напрямую. Пример: если class A имеет метод, который ожидает указатель на объект типа class B, тогда этот метод должен действительно ожидать объект типа abstract base class of B. Это помогает также открывать и закрывать.

При условии, что я правильно понял, мой вопрос будет , это хорошая практика применить это к взаимодействию классов all , или я должен попытаться подумать с точки зрения слоев

Я скептически отношусь к тому, что мы платим за это, следуя этому принципу. Скажем, мне нужно реализовать функцию Z. После анализа я пришел к выводу, что функция Z состоит из функциональности A, B и C , Я создаю фасад class Z, который через интерфейсы использует классы A, B и C. Я начинаю кодировать реализацию, и в какой-то момент я понимаю, что задача Z фактически состоит из функциональности A, B и D. Теперь мне нужно отказаться от интерфейса C, C и напишите отдельный D интерфейс и класс. Без интерфейсов только класс должен был быть заменен.

Другими словами, чтобы что-то изменить, мне нужно изменить 1. вызывающего 2. интерфейс 3. декларация 4. реализация. В реализации с прямым соединением с python мне нужно будет изменить только реализацию.

41 голос | спросил Vorac 25 FebruaryEurope/MoscowbWed, 25 Feb 2015 21:26:36 +0300000000pmWed, 25 Feb 2015 21:26:36 +030015 2015, 21:26:36

7 ответов


84

Во многих мультфильмах или других средствах массовой информации силы добра и зла часто иллюстрируются ангелом и демоном, сидящим на плечах персонажа. В нашей истории здесь, вместо добрых и злых, у нас есть СОЛИД на одном плече, а ЯГНИ (тебе это не понадобится!), Сидя на другом.

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

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

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

ответил whatsisname 25 FebruaryEurope/MoscowbWed, 25 Feb 2015 22:27:52 +0300000000pmWed, 25 Feb 2015 22:27:52 +030015 2015, 22:27:52
11

В словах непрофессионала:

Применение DIP - это просто и весело . Не получить дизайн сразу при первой попытке не является достаточной причиной отказа от DIP в целом.

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

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

Некоторые люди говорят, что это добавляет сложности, но я думаю, что opossite верен. Даже для небольших проектов. Это облегчает тестирование /издевку. Это делает ваш код меньше, если какие-либо инструкции case или вложенный ifs. Это уменьшает циклическую сложность и заставляет вас думать по-новому. Это делает программирование более похожим на реальный дизайн и производство.

ответил Tulains Córdova 25 FebruaryEurope/MoscowbWed, 25 Feb 2015 21:31:40 +0300000000pmWed, 25 Feb 2015 21:31:40 +030015 2015, 21:31:40
9

Используйте инверсию зависимостей , где это имеет смысл.

Один экстремальный контрпример - это класс «строка», включенный во многие языки. Он представляет собой примитивную концепцию, по существу массив символов. Предполагая, что вы можете изменить этот основной класс, нет смысла использовать DI здесь, потому что вам никогда не придется менять внутреннее состояние с чем-то другим.

Если у вас есть группа объектов, используемых внутри модуля, которые не подвергаются другим модулям или не используются в любом месте, вероятно, не стоит использовать DI.

Есть два места, где DI должен автоматически использоваться по моему мнению:

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

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

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

DI - отличный инструмент, но, как и любой инструмент *, его можно злоупотреблять или использовать неправильно.

* Исключение из приведенного выше правила: поршневая пила - идеальный инструмент для любой работы. Если проблема не решена, она удалит ее. Постоянно. Суб>

ответил 25 FebruaryEurope/MoscowbWed, 25 Feb 2015 21:58:05 +0300000000pmWed, 25 Feb 2015 21:58:05 +030015 2015, 21:58:05
5

Мне кажется, что в исходном вопросе отсутствует часть точки DIP.

  

Я скептически отношусь к тому, что мы платим за это, следуя этому принципу. Скажем, мне нужно реализовать функцию Z. После анализа я пришел к выводу, что функция Z состоит из функциональности A, B и C. Я создаю фасадный класс Z, который через интерфейсы использует классы A, B и C. Я начинаю кодирование и в какой-то момент я понимаю, что задача Z фактически состоит из функциональности A, B и D. Теперь мне нужно отказаться от интерфейса C, прототипа класса C и написать отдельный интерфейс и класс D. Без интерфейсов требуется только замена класса.

Чтобы по-настоящему воспользоваться DIP, сначала вы должны создать класс Z и вызвать функциональность классов A, B и C (которые еще не разработаны). Это дает вам API для классов A, B и C. Затем вы идете и создаете классы A, B и C и заполняете детали. Вы должны эффективно создавать абстракции, которые вам нужны, поскольку вы создаете класс Z, основываясь исключительно на том, что требуется классу Z. Вы даже можете писать тесты вокруг класса Z до того, как будут записаны классы A, B или C.

Помните, что DIP говорит, что «модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций».

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

Никогда не будет класса D, потому что вы разработали, что Z нуждается в A, B и C, прежде чем они будут написаны. Изменение требований - это совсем другая история.

ответил Stephen 26 FebruaryEurope/MoscowbThu, 26 Feb 2015 04:02:03 +0300000000amThu, 26 Feb 2015 04:02:03 +030015 2015, 04:02:03
5

Короткий ответ «почти никогда», но на самом деле есть несколько мест, где DIP не имеет смысла:

  1. Заводы или строители, работа которых заключается в создании объектов. Это, по сути, «листовые узлы» в системе, которая полностью охватывает IoC. В какой-то момент что-то должно фактически создавать ваши объекты и не может зависеть от чего-либо еще. На многих языках контейнер IoC может сделать это за вас, но иногда вам нужно сделать это по-старому.

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

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

  4. Любые объекты, которые предоставляются в качестве внешнего API и должны создавать другие объекты, сведения о реализации которых вы не хотите публиковать публично. Это подпадает под общую категорию «дизайн библиотеки отличается от дизайна приложения». Библиотека или структура могут свободно использовать DI внутри, но в конечном итоге придется выполнять некоторую фактическую работу, иначе это не очень полезная библиотека. Предположим, вы разрабатываете сетевую библиотеку; вы действительно not хотите, чтобы потребитель мог обеспечить собственную реализацию сокета. Вы можете внутренне использовать абстракцию сокета, но API, который вы предоставляете вызывающим, будет создавать свои собственные сокеты.

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

Там может быть больше; это те, которые я часто наблюдаю на несколько .

ответил Aaronaught 26 FebruaryEurope/MoscowbThu, 26 Feb 2015 08:20:11 +0300000000amThu, 26 Feb 2015 08:20:11 +030015 2015, 08:20:11
2

Некоторые признаки того, что вы можете применять DIP на слишком микро уровне, где он не обеспечивает значение:

  • у вас есть C /CImpl или IC /C пара, только с одной реализацией этого интерфейса
  • подписи в вашем интерфейсе и реализации соответствуют друг другу (нарушая принцип DRY)
  • вы часто меняете C и CImpl одновременно.
  • C является внутренним для вашего проекта и не используется вне вашего проекта в качестве библиотеки.
  • Вы разочарованы F3 в Eclipse /F12 в Visual Studio, перейдя к интерфейсу вместо фактического класса

Если это то, что вы видите, вам может быть лучше, если вы просто запустите Z-вызов C и пропустите интерфейс.

Кроме того, я не думаю об украшении методов с помощью инфраструктуры вложения /динамического прокси-сервера зависимостей (Spring, Java EE) так же, как и истинный SOLID DIP - это больше похоже на деталь реализации того, как оформление метода работает в этой технологии стек. Сообщество Java EE считает улучшение , что вам не нужны пары Foo /FooImpl, как вы привыкли ( ссылка ). Напротив, Python поддерживает функцию декодирования в качестве первоклассной языковой функции.

См. также это сообщение в блоге .

ответил wrschneider 5 42015vEurope/Moscow11bEurope/MoscowThu, 05 Nov 2015 00:48:24 +0300 2015, 00:48:24
0

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

Точкой инверсии зависимостей является выборочно инвертировать зависимости, которые заставляют вещи запутываться. Те, которые должны перейти от A к B в C, все еще делают это, это те, которые переходили из C в A, которые теперь идут от A до C.

Результатом должен быть граф зависимостей, свободный от циклов - DAG. Существуют различные tools , который проверит это свойство и нарисует график.

Для более полного объяснения см. эту статью :

  

Суть правильного применения принципа инверсии зависимостей такова:

     

Разделите код /​​сервис /â € | вы зависнете от интерфейса и реализации.   Интерфейс реструктурирует зависимость на жаргоне используемого кода, реализация реализует его с точки зрения его основных методов.

     

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

ответил soru 26 FebruaryEurope/MoscowbThu, 26 Feb 2015 01:32:31 +0300000000amThu, 26 Feb 2015 01:32:31 +030015 2015, 01:32:31

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132