Чи погана практика передавати екземпляри через кілька шарів?


60

У своєму дизайні програми я часто приходжу до того, що мені доводиться передавати екземпляри об'єктів через кілька класів. Наприклад, якщо у мене є контролер, який завантажує аудіофайл, а потім передає його програвачеві, а плеєр передає його програвачевіRunnable, який передає його знову кудись інше і т. Д. Це виглядає погано, але я не знати, як цього уникнути. Або добре це робити?

EDIT: Можливо, приклад програвача не найкращий, тому що я міг би завантажити файл пізніше, але в інших випадках це не працює.

Відповіді:


54

Як зазначали інші, це не обов'язково є поганою практикою, але ви повинні звернути увагу, що ви не порушуєте розділення проблем між шарами та передачу конкретних для шарів примірників між шарами. Наприклад:

  • Об'єкти бази даних ніколи не повинні передаватися на більш високі шари. Я бачив програми, які використовують клас .NET DataAdapter, клас доступу до БД і передають його на рівень інтерфейсу користувача, а не використовують DataAdapter в DAL, створюють DTO або набір даних і передають його. Доступ до БД - це домен DAL.
  • Об'єкти інтерфейсу, звичайно, повинні бути обмежені шаром інтерфейсу користувача. Знову я бачив, що це порушено, як із ListBoxes, заповненими даними користувачів, переданими на рівень BL, замість масиву / DTO його вмісту, так і (моїм улюбленим моїм улюбленим), класом DAL, який отримував ієрархічні дані з БД, а не повертає ієрархічну структуру даних, він просто створив і заповнив об’єкт TreeView і передав його назад до інтерфейсу користувача, щоб динамічно додавати до форми.

Однак якщо екземпляри, які ви передаєте, - це DTO або організації, це, мабуть, нормально.


1
Це може здатися шокуючим, але в темні перші дні .NET, як правило, рекомендована практика і, мабуть, краща за те, що роблять більшість інших стеків.
Wyatt Barnett

1
Я не погоджуюсь. Це правда, що Microsoft схвалила практику одношарових додатків, де клієнт Winforms також отримав доступ до БД, а DataAdapter додали безпосередньо до форми як невидимий елемент керування, але це лише специфічна архітектура, відмінна від N-ярусного налаштування ОП . Але в багатошаровій архітектурі, і це було справедливо для VB6 / DNA ще до .NET, об'єкти DB залишилися в шарі DB.
Авнер Шахар-Каштан

Для уточнення: ви бачили людей, які звертаються безпосередньо до інтерфейсу користувача (у вашому прикладі, списки списків) зі свого "рівня даних"? Я не думаю, що я натрапив на таке порушення у виробничому коді .. вау ..
Саймон Уайтхед

7
@SimonWhitehead саме так. Люди, які нечітко розрізняли різницю між ListBox і масивом, і використовували ListBoxes як DTO. Це був момент, який допоміг мені зрозуміти, скільки невидимих ​​припущень я роблю, які не є інтуїтивно зрозумілими для інших.
Авнер Шахар-Каштан

1
@SimonWhitehead - Так, прикмет, я бачив, що в програмах VB6 і VB.NET Framework 1.1 і 2.0, і мені було доручено підтримувати таких монстрів. Це стає дуже потворно, дуже швидко.
jfrankcarr

15

Цікаво, що про незмінні об’єкти ще ніхто не говорив . Я б заперечував, що передача непорушного об'єкта навколо всіх різних шарів насправді є хорошою справою, а не створенням безлічі короткотривалих об’єктів для кожного шару.

Ерік Ліпперт у своєму блозі має великі дискусії про незмінність

З іншого боку, я б стверджував, що передача змінних об'єктів між шарами - це погана конструкція. Ви по суті будуєте шар із обіцянкою, що навколишні шари не змінять його таким чином, щоб зламати код.


13

Передача екземплярів об'єкта навколо - це нормальна річ. Це зменшує необхідність збереження стану (тобто змінних екземплярів) і відв'язує код від контексту його виконання.

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


6
Я б сказав, що ще одна проблема, з якою ви можете зіткнутися, стосується незмінності. Я пам'ятаю дуже здивовальну помилку в проекті, над яким я працював, де розробник змінив DTO, не замислюючись про те, що його конкретний клас був не єдиним із посиланням на цей об’єкт.
Філ

8

Може бути незначним, але існує ризик привласнення цієї посилання десь в одному з шарів, що може спричинити звисання посилання або витоку пам'яті.


Ваша думка правильна, але з термінології OP ("передачі екземплярів об'єкта") я маю відчуття, що або він передає значення (а не покажчики), або він говорив про середовище, зібране сміттям (Java, C #, Python, Go,. ..).
Мохаммед Деган

7

Якщо ви передаєте об'єкти навколо просто тому, що це потрібно у віддаленому районі коду, використання інверсії моделей управління та введення залежності залежно від необхідного відповідного контейнера IoC може вирішити проблеми, пов'язані з перенесенням екземплярів об'єкта навколо. Я використовував його в середньому проекті і ніколи більше не розглядав би можливість написання великого фрагмента коду сервера, не використовуючи його.


Це звучить цікаво, я вже використовую конструктор впорскування, і я думаю, що мої компоненти високого рівня контролюють компоненти низького рівня. Як ви використовуєте контейнер IOC, щоб уникнути перенесення екземплярів?
Puckl

Я думаю, що тут я знайшов відповідь: martinfowler.com/articles/injection.html
Puckl

1
Побічна примітка, якщо ви працюєте на Java, Guice дуже приємний, і ви можете розширити свої прив’язки до таких речей, як запити, щоб компонент високого рівня створював область і прив'язував екземпляри до правильних класів у той час.
Дейв

4

Передача даних через купу шарів не є поганою справою, адже це дійсно єдиний спосіб, коли багатошарова система може працювати, не порушуючи шарувату структуру. Ознакою, що є проблеми, є те, коли ви передаєте свої дані декільком об'єктам в одному шарі для досягнення своєї мети.


3

Швидкий відповідь: немає нічого поганого в передачі екземплярів предметів. Як уже згадувалося, суть полягає в тому, щоб пропустити призначення цього посилання у всіх шарах, що потенційно може спричинити звисання посилань або витоків пам'яті.

У наших проектах ми використовуємо цю практику для передачі DTO (об'єкт передачі даних) між шарами, і це дуже корисно. Ми також повторно використовуємо наші об'єкти dto для побудови більш складних один раз, як для зведеної інформації.


3

Я в першу чергу розробник веб-інтерфейсу, але мені це здається, що ваш інтуїтивний дискомфорт може бути меншим, ніж проходження екземпляра, і більше про те, що ви збираєтеся трохи процедурувати з цим контролером. Чи повинен ваш контролер потіти всіма цими деталями? Чому він навіть посилається на більше ніж одне ім'я іншого об'єкта для отримання звуку?

У дизайні ООП я схильний думати з приводу того, що є вічнозеленим і що, швидше за все, може зазнати змін. Тема, яку потрібно змінити, - це те, що ви хочете, як правило, розміщувати у великих об'єктних полях, щоб ви могли підтримувати послідовні інтерфейси навіть під час зміни гравців або додавання нових параметрів. Або ви виявляєте, що хочете оптом обміняти аудіооб'єкти або компоненти.

У цьому випадку ваш контролер повинен визначити, що потрібно відтворювати аудіофайл, а потім мати послідовний / вічнозелений спосіб його відтворення. З іншого боку, аудіоплеєр може легко змінитися, коли технологія та платформи змінюються або додаються нові варіанти. Усі ці деталі повинні розташовуватися під інтерфейсом більшого складеного об'єкта IMO, і вам не доведеться переписувати контролер, коли деталі того, як відтворюється звук, змінюються. Потім, коли ви передаєте екземпляр об'єкта з такими деталями, як розташування файлів, у більший об'єкт, все, що відбувається обмін, робиться в інтер'єрі відповідного контексту, коли хтось менш схильний зробити щось з цим нерозумно.

Так що в цьому випадку я не думаю, що саме цей предметний екземпляр може кидатися навколо, що може вас клопотати. Це те, що капітан Пікар бігає вниз до машинного відділення, щоб увімкнути основу основи, біжить назад до мосту, щоб намітити координати, а потім натискаючи кнопку "пробий" після вмикання щитів, а не просто кажучи "Візьміть нас на планету X в Warp 9. Зробіть так ". і дозволяючи його екіпажу розібратися в деталях. Тому що, коли він обробляє це таким чином, він може капітан будь-якого корабля флоту, не знаючи компонування кожного корабля і як все працює. І це, в кінцевому рахунку, найбільший виграш проекту OOP, IMO.


2

Це досить поширений дизайн, який закінчується, хоча у вас можуть виникнути проблеми із затримкою, якщо ваш додаток чутливий до подібних речей.


2

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


2

Як вказували інші відповіді, це не сама по собі конструкція. Це може створити щільне з'єднання між вкладеними класами та тими, хто їх вкладає, але розпушування з’єднання може бути неприйнятним варіантом, якщо вкладення посилань надає значення дизайну.

Одне з можливих рішень - «вирівняти» вкладені посилання в класі контролера.

Замість того, щоб кілька разів передавати параметр через вкладені об'єкти, ви могли підтримувати в класі контролера посилання на всі вкладені об'єкти.

Те, як саме це реалізовано (або якщо це навіть дійсне рішення), залежить від поточної конструкції системи, наприклад:

  • Чи можете ви підтримувати якусь карту вкладених об'єктів у контролері, не ускладнюючись?
  • Коли ви передаєте параметр відповідному вкладеному об'єкту, чи може вкладений об'єкт розпізнати параметр негайно, чи з’явилася додаткова функціональність під час передачі його через вкладені об'єкти?
  • тощо.

Це питання, з яким я стикався в схемі дизайну MVC для клієнта GXT. Наші компоненти GUI містили вкладені компоненти GUI для декількох шарів. Коли дані моделі були оновлені, ми закінчували її передачу через декілька шарів, поки вони не досягли відповідного компонента (компонентів). Це створило небажане з'єднання між компонентами графічного інтерфейсу, тому що, якщо ми хотіли, щоб новий клас компонентів GUI прийняв моделі даних, нам довелося створити методи оновлення даних моделі у всіх компонентах GUI, які містили новий клас.

Щоб виправити це, ми зберегли в класі View карта посилань на всі вкладені компоненти GUI, щоб кожен раз, коли дані моделі оновлювались, Перегляд міг надсилати оновлені дані моделі безпосередньо до потрібних компонентів GUI, кінець історії . Це спрацювало добре, оскільки були лише поодинокі екземпляри кожного компонента GUI. Я міг бачити, що вона не працює настільки добре, якщо було кілька примірників деяких компонентів графічного інтерфейсу, що ускладнює визначення того, яку копію потрібно оновити.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.