Проблематично ли иметь зависимость между объектами одного уровня в многоуровневой архитектуре программного обеспечения?

12

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

Но я не уверен, что думать об объектах, которые зависят от других объектов того же слоя.

В качестве примера, давайте предположим, что приложение с тремя слоями и несколькими объектами, такими как один на изображении. Очевидно, что сверху вниз (зеленые стрелки) в порядке, снизу вверх (красная стрелка) не в порядке, но как насчет зависимости внутри того же слоя (желтая стрелка)?

введите описание изображения здесь

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

bracco23
источник
Если у вас нет циклов, в слое, где у вас есть горизонтальные связи, существует разбиение на подслои, которые имеют только зависимости между слоями
max630

Ответы:

10

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

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

Например, предположим, что у вас есть «горизонтально-слоистая система» с уровнем базы данных, бизнес-уровнем и уровнем пользовательского интерфейса (UI). Допустим, что слой пользовательского интерфейса содержит как минимум несколько десятков различных диалоговых классов.

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

Это все возможные варианты дизайна, возможно, более или менее разумные в зависимости от типа системы, которую вы строите, но ни один из них не делает «слоистость» вашей системы недействительной.

Док Браун
источник
Продолжая на примере пользовательского интерфейса, каковы плюсы и минусы наличия внутренних зависимостей? Я вижу, что это облегчает повторное использование и введение циклических зависимостей, что может быть проблемой в зависимости от используемого метода DI. Что-нибудь еще?
bracco23
@ bracco23: плюсы и минусы наличия «внутренних» зависимостей совпадают с плюсами и минусами наличия произвольных зависимостей. «Минусы» заключаются в том, что они усложняют повторное использование компонентов и их труднее тестировать, особенно изолированно от других компонентов. Плюсы - явные зависимости делают вещи, которые требуют сплоченности, проще в использовании, понимании, управлении и тестировании.
Док Браун
14

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

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

Так например, Obj 1не должно зависеть от Obj 3. Он должен зависеть, например, IObj 3и должен быть указан, с какой реализацией этой абстракции он будет работать во время выполнения. То, что делает рассказывание, не должно быть связано ни с одним из уровней, так как его работа состоит в том, чтобы отобразить эти зависимости. Это может быть контейнер IoC, пользовательский код, называемый, например, mainкоторый использует чистый DI. Или на толчке это может быть даже сервисный локатор. Независимо от этого, зависимости не существуют между слоями, пока эта вещь не обеспечивает отображение.

Но я не уверен, что думать об объектах, которые зависят от других объектов того же слоя.

Я бы сказал, что это единственный раз, когда у вас должны быть прямые зависимости. Он является частью внутренней работы этого слоя и может быть изменен, не затрагивая другие слои. Так что это не вредная связь.

Дэвид Арно
источник
Спасибо за ответ. Да, слой должен показывать не объекты, а интерфейсы, я знаю это, но я упустил это для простоты.
Bracco23
4

Давайте посмотрим на это практически

введите описание изображения здесь

Obj 3теперь знает, Obj 4существует. Ну и что? Почему мы заботимся?

DIP говорит

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

Хорошо, но не все ли абстракции объектов?

DIP также говорит

«Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций».

Хорошо, но если мой объект правильно инкапсулирован, разве это не скрывает какие-либо детали?

Некоторые люди любят слепо настаивать на том, что каждому объекту нужен интерфейс с ключевыми словами. Я не один из них. Мне нравится слепо настаивать на том, что если вы не собираетесь использовать их сейчас, вам нужен план, чтобы справиться с чем-то вроде них позже.

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

Obj 3знает, что Obj 4существует. Но Obj 3знает ли Obj 4бетон?

Вот почему вот так приятно НЕ распространяться newповсюду. Если Obj 3не знает, Obj 4конкретен ли он, скорее всего потому, что он его не создал, тогда, если вы прокрались позже и превратились Obj 4в абстрактный класс, Obj 3все равно.

Если вы можете сделать это, то Obj 4все время был полностью абстрактным. Единственное, что создает интерфейс между ними с самого начала, - это уверенность в том, что кто-то случайно не добавит код, который выдает Obj 4конкретный прямо сейчас. Защищенные конструкторы могут снизить этот риск, но это приводит к другому вопросу:

Есть ли Obj 3 и Obj 4 в одной упаковке?

Объекты часто так или иначе группируются (пакет, пространство имен и т. Д.). При разумной группировке более вероятно изменение воздействия внутри группы, а не между группами.

Мне нравится группировать по характеристикам. Если Obj 3и Obj 4находятся в той же группе и слой это очень маловероятно , что вы опубликовали один и не хотите , чтобы реорганизовать его в то время как требуется изменять только другой. Это означает, что эти объекты с меньшей вероятностью получат выгоду от размещения абстракции между ними до того, как в этом возникнет явная необходимость.

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

Это должно быть так просто, но, к сожалению, и Java, и C # сделали неудачный выбор, который усложняет это.

В C # принято называть каждый интерфейс ключевого слова Iпрефиксом. Это заставляет клиентов ЗНАТЬ, что они говорят с интерфейсом ключевого слова. Это портит план рефакторинга.

В Java традиционно используется лучший шаблон именования: FooImple implements Fooоднако это помогает только на уровне исходного кода, поскольку Java компилирует интерфейсы ключевых слов в другой двоичный файл. Это означает, что при рефакторинге Fooот конкретных к абстрактным клиентам, которым не требуется ни одного измененного символа, все равно придется перекомпилироваться.

Именно эти ОШИБКИ на этих конкретных языках не позволяют людям откладывать формальное абстрагирование до тех пор, пока им это действительно не нужно. Вы не сказали, какой язык используете, но понимаете, что есть языки, у которых просто нет этих проблем.

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

Принцип ЯГНИ играет здесь ключевую роль. Но так же: «Пожалуйста, сделайте так, чтобы мне было трудно выстрелить себе в ногу».

candied_orange
источник
0

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

Например, с точки зрения правила зависимости . DR - это правило, предложенное Робертом К. Мартином для его знаменитой « Чистой архитектуры» .

Это говорит

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

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

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

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

Другая точка зрения - SRP.

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

LAIV
источник