Почему "защита от изменений" подразумевает направление зависимости?

Немного предыстории: я программист-самоучка, который начал работать на Python и изучал Java, когда я присоединился к MegaCorp(TM) 6 лет назад. Имея степень по математике, я довольно солиден (без каламбура) по алгоритмам и критическому мышлению, но я часто нахожусь с пробелом в знаниях, связанных со структурами данных, дизайном или другими основами CompSci, которые мои коллеги изучили в своей информатике курсы.

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

Я прохожу около трети пути и действительно смущен одним из основных мотивирующих факторов предложений. Дядя Боб представляет многие идеи и принципы (в том числе принципы SOLID, о которых я слышал ранее, хотя я все еще сталкиваюсь с принципом подстановки Лискова) как предназначенные для "защиты" некоторой части системы от требований изменить. Есть несколько примеров этого, но самый ясный на странице 73:

Если компонент A должен быть защищен от изменений в компоненте B, тогда компонент B должен зависеть от компонента A.

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

Это утверждение не имеет никаких доказательств, и это не самоочевидно для меня. Рассмотрим случай класса ClassA в компоненте (упаковке) ComponentA какие звонки DoStuffReturn doStuff(DoStuffInput input, String someOtherArg) в ClassB в компоненте ComponentB - и при этом вызов осуществляется либо через прямую зависимость, либо через зависимость от интерфейса в ComponentB (не в ComponentA, как советует Чистая Архитектура)

  • Если изменение в B является функциональным изменением, а не изменением подписи (т. Е. Для того же DoStuffInput а также String вход, другой DoStuffReturn возвращается), то в A не требуется никаких изменений:
    • ClassA призыв к ClassB.doStuff остается действительным (те же аргументы и тип возвращаемого значения)
    • ClassA Юнит-тесты (которые должны быть использованы по изданию ClassB s) должен пройти
    • Любые функциональные / интеграционные тесты, которые проверяют, как ClassA а также ClassB сотрудничать нужно будет обновить свои ожидания, но это не изменение ComponentA (если эти тесты не находятся в ComponentA - Я обычно видел их в ComponentAIntegrationTests пакет, но я думаю, что они также могут быть расположены вместе. Похоже, что это не то, что предлагает книга - похоже, речь идет об изменениях в коде, а не в тестах)
  • Если изменение в B является изменением подписи на метод, отличный от doStuff, то А не потребует никаких изменений
  • Если изменение в B является изменением подписи на doStuff, тогда A потребует изменения - но это было бы так, если бы интерфейс тоже был в A.

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

Что мне не хватает? Если ComponentA зависит от ComponentB, при каких обстоятельствах изменение класса в ComponentB потребует изменения в ComponentA?

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

2 ответа

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

Компоненты - это единицы развертывания. Это самые маленькие объекты, которые могут быть развернуты как часть системы. В Java это файлы jar. - страница 96

Первое, на что нужно обратить внимание, это то, что все зависимости являются зависимостями исходного кода. Стрелка, указывающая из класса A в класс B, означает, что в исходном коде класса A упоминается имя класса B, а в классе B ничего не говорится о классе A. - стр. 72

Тогда возникает вопрос, от каких изменений мы хотим защитить компоненты (банки)? И ответ таков: перекомпиляция, перераспределение и переходные зависимости.

Если компонент A зависит от компонента B тогда любое изменение в B (даже если это изменение не влияет на API, который A потребляет) требует A перекомпилировать. Кроме того, если изменение в B добавляет, удаляет или изменяет зависимость B, затем A должны согласовать новую транзитивную зависимость, и поэтому изменение распространяется.

Эта зависимость означает, что изменение исходного кода [ B ] заставит [ A ] для перекомпиляции и повторного развертывания, хотя ничего, о чем оно заботилось, на самом деле не изменилось. - страница 84

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

Я объединяю элементы более чем одного из принципов SOLID здесь; но они имеют общую общность, когда дело доходит до обработки изменений.

Что касается моего прошлого, у меня есть степень по компьютерным наукам и более 20 лет профессионального опыта.

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

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

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

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

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

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

Другие вопросы по тегам