Это плохая практика, чтобы пропустить экземпляры через несколько слоев?

60

При разработке моей программы я часто дохожу до того, что мне приходится передавать экземпляры объекта через несколько классов. Например, если у меня есть контроллер, который загружает аудиофайл, а затем передает его проигрывателю, а проигрыватель передает его в playerRunnable, который снова передает его в другое место и т. Д. Это выглядит довольно плохо, но я не знать, как этого избежать. Или это нормально?

РЕДАКТИРОВАТЬ: Может быть, пример плеера не самый лучший, потому что я мог загрузить файл позже, но в других случаях это не работает.

Puckl
источник

Ответы:

54

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

  • Объекты базы данных никогда не должны передаваться на более высокие уровни. Я видел программы, использующие класс .NET DataAdapter, класс доступа к БД и передающие его на уровень пользовательского интерфейса вместо использования DataAdapter в DAL, создания DTO или набора данных и передачи его. Доступ к БД является доменом DAL.
  • Конечно, объекты пользовательского интерфейса должны быть ограничены уровнем пользовательского интерфейса. Опять же, я видел, что это нарушено, и с ListBox, заполненным пользовательскими данными, переданными на уровень BL, вместо массива / DTO его содержимого, и (мой любимый), класс DAL, который извлекал иерархические данные из вместо того, чтобы возвращать иерархическую структуру данных, она просто создала и заполнила объект TreeView и передала его обратно в пользовательский интерфейс для динамического добавления в форму.

Однако, если экземпляры, которые вы передаете, являются DTO или самими сущностями, это, вероятно, нормально.

Авнер Шахар-Каштан
источник
1
Это может звучать шокирующе, но в темные первые дни .NET это было рекомендуемой практикой и, вероятно, было лучше, чем то, что делает большинство других стеков.
Уайетт Барнетт
1
Я не согласен. Это правда, что Microsoft одобрила практику однослойных приложений, в которых клиент Winforms также обращался к БД, а DataAdapter был добавлен непосредственно в форму в качестве невидимого элемента управления, но это всего лишь особая архитектура, отличающаяся от настройки N-уровня OP. , Но в многослойной архитектуре, и это было верно для VB6 / DNA еще до .NET, объекты БД оставались на уровне БД.
Авнер Шахар-Каштан
Для пояснения: вы видели, как люди обращались к пользовательскому интерфейсу напрямую (в вашем примере со списками) с их «уровня данных»? Я не думаю, что я сталкивался с таким нарушением в производственном коде .. вау ..
Саймон Уайтхед
7
@SimonWhitehead Точно. Люди, которые не понимали различия между ListBox и массивом, использовали ListBoxes в качестве DTO. Это был момент, который помог мне понять, сколько невидимых предположений я делаю, которые не интуитивны для других.
Авнер Шахар-Каштан
1
@SimonWhitehead - Да, довольно давно я видел это в программах VB6 и VB.NET Framework 1.1 и 2.0, и мне было поручено поддерживать таких монстров. Это становится очень уродливо, очень быстро.
jfrankcarr
15

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

У Эрика Липперта есть несколько замечательных обсуждений неизменности в его блоге.

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

М Афифи
источник
13

Передача экземпляров объекта - нормальная вещь. Это уменьшает потребность в сохранении состояния (т. Е. Переменных экземпляра) и отделяет код от контекста его выполнения.

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

dasblinkenlight
источник
6
Я бы сказал, что другая проблема, с которой вы можете столкнуться, связана с неизменностью. Я могу вспомнить одну очень запутанную ошибку в проекте, над которым я работал, когда разработчик модифицировал DTO, не задумываясь о том, что его конкретный класс был не единственным со ссылкой на этот объект.
Фил
8

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

techfoobar
источник
Ваша точка зрения верна, но из терминологии OP («передача экземпляров объекта») у меня есть ощущение, что он либо передает значения (а не указатели), либо говорил о среде со сборщиком мусора (Java, C #, Python, Go,. ..).
Мохаммад Дехган
7

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

Стивен Шланскер
источник
Это звучит интересно, я уже использую инъекцию конструктора, и я думаю, что мои компоненты высокого уровня управляют компонентами низкого уровня. Как вы используете контейнер IOC, чтобы избежать переноса экземпляров?
Puckl
Я думаю, что нашел ответ здесь: martinfowler.com/articles/injection.html
Puckl
1
Примечание: если вы работаете в Java, Guice действительно хорош, и вы можете связать свои привязки с такими вещами, как запросы, чтобы компонент высокого уровня создал область и связал экземпляры с правильными классами в это время.
Дэйв
4

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

Ryathal
источник
3

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

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

Е.Л. Юсубов
источник
3

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

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

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

Так что в этом случае я не думаю, что этот экземпляр объекта может вызвать у вас беспокойство. Это то, что капитан Пикард бежит в машинное отделение, чтобы включить ядро ​​основы, бежит обратно к мосту, чтобы построить координаты, а затем нажимает кнопку «пробить» после включения щитов, а не просто говорит «Возьми нас на планету X в Warp 9. Сделай так. " и позволить своей команде разобраться в деталях. Потому что, когда он справляется с этим, он может управлять любым кораблем во флоте, не зная расположения каждого корабля и как все работает. И это, в конечном счете, самая большая победа в ООП-дизайне - IMO.

Эрик Реппен
источник
2

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

Джеймс
источник
2

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

Kaz
источник
2

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

Одним из возможных решений является «выравнивание» вложенных ссылок в классе контроллера.

Вместо того, чтобы передавать параметр несколько раз через вложенные объекты, вы можете поддерживать в классе контроллера ссылки на все вложенные объекты.

То, как именно это реализовано (или если это даже правильное решение), зависит от текущего проекта системы, такого как:

  • Можете ли вы поддерживать какую-то карту вложенных объектов в контроллере, не усложняя ее?
  • Когда вы передаете параметр соответствующему вложенному объекту, может ли вложенный объект немедленно распознать параметр, или при передаче его через вложенные объекты возникли дополнительные функции?
  • и т.п.

Это проблема, с которой я столкнулся в шаблоне проектирования MVC для клиента GXT. Наши компоненты GUI содержали вложенные компоненты GUI для нескольких уровней. Когда данные модели были обновлены, мы в конечном итоге пропустили их через несколько слоев, пока они не достигли соответствующих компонентов. Это создало нежелательную связь между компонентами GUI, потому что, если мы хотели, чтобы новый класс компонента GUI принимал данные модели, нам нужно было создать методы для обновления данных модели во всех компонентах GUI, которые содержали новый класс.

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

Давид Качиньский
источник
0

То, что вы описываете - это шаблон проектирования под названием « Цепочка ответственности» . Apple использует этот шаблон для своей системы обработки событий, чего бы это ни стоило.

user8865
источник