Чому для нижчих шарів додатків добре не знати про «вищі»?


66

У типовому (добре розробленому) веб-додатку MVC база даних не знає код моделі, код моделі не знає код контролера, а код контролера не знає про код перегляду. (Я думаю, ви навіть можете почати так далеко, як апаратне забезпечення, а може бути, і далі.

Йдучи в іншому напрямку, ви можете піти лише на один шар вниз. Вигляд може знати про контролер, але не про модель; контролер може бути в курсі моделі, але не бази даних; модель може знати про базу даних, але не про ОС. (Що-небудь глибше, мабуть, не має значення.)

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


10
Можливо, це тому, що дані "надходять" з бази даних до перегляду. Він "запускається" в базі даних і "надходить" на подання. Поінформованість рівня проходить у зворотному напрямку, оскільки дані "подорожують". Мені подобається використовувати "цитати".
Джейсон Світт

1
Ви позначили це в останньому реченні: Односпрямований. Чому зв'язані списки набагато більш типові, ніж подвійно пов'язані списки? Підтримка відносин стає нескінченно простішою з однозначно пов'язаним списком. Ми створюємо графіки залежності таким чином, оскільки рекурсивні дзвінки стають набагато менш імовірними, а загальні характеристики графів стають легшими міркувати про загалом. Розумні структури за своєю суттю більш ремонтабельні, і ті ж речі, що впливають на графіки на мікрорівні (реалізація), роблять це так само, як і на макрорівні (архітектура).
Джиммі Хоффа

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

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

1
Очевидно, що представлення знає сильно набрану модель перегляду.
DazManCat

Відповіді:


121

Шари, модулі, власне архітектура, є засобом полегшення розуміння комп'ютерними програмами людиною . Чисельно оптимальним методом вирішення проблеми майже завжди є непристойно заплутаний безлад безмодульного, самопосилаючогося або навіть самомодулюючого коду - будь то сильно оптимізований код асемблера у вбудованих системах із калічними обмеженнями пам’яті чи послідовностями ДНК через мільйони років тиску відбору. Такі системи не мають шарів, не помітного напряму потоку інформації, насправді немає структури, яку ми б могли взагалі не помітити. Всім, окрім їхнього автора, вони, здається, працюють за допомогою чистої магії.

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

Але неминуче модулі повинні викликати функції один від одного, і шари повинні створюватися один над одним. Тому на практиці завжди потрібно будувати системи так, щоб одні частини вимагали інших деталей. Кращий компроміс полягає в тому, щоб будувати їх таким чином, що одна частина вимагає іншої, але ця частина не потребує першої назад. І саме це дає нам однонаправлене шарування: зрозуміти схему бази даних, не знаючи правил бізнесу, і зрозуміти правила бізнесу, не знаючи про користувальницький інтерфейс. Було б непогано мати незалежність в обох напрямках - дозволяти комусь програмувати новий інтерфейс, нічого не знаючивзагалі про правила ведення бізнесу - але на практиці це практично ніколи не можливо. Такі правила, як "Ніяких циклічних залежностей" або "Залежності повинні досягати лише одного рівня", просто фіксують практично досяжну межу основної ідеї, яку зрозуміти одну річ за часом легше, ніж дві речі.


1
Що ви маєте на увазі під "розумінням системи зрозумілою для нормальних людей "? Я думаю, що таке словосполучення заохочує нових програмістів відкидати ваші добрі бали, тому що, як і більшість людей, вони вважають, що вони розумніші за більшість людей, і це не буде для них проблемою. Я б сказав "зробити систему зрозумілою для людей"
Томас Боніні

12
Це потрібно для читання для тих, хто вважає повне розв’язування ідеальним, до якого слід прагнути, але не може зрозуміти, чому це не працює.
Роберт Харві

6
Ну, @Andreas, завжди Мел .
TRiG

6
Я думаю, що "легше зрозуміти" недостатньо. Йдеться також про полегшення зміни, розширення та підтримки коду.
Майк Веллер

1
@Peri: такий закон існує, див. En.wikipedia.org/wiki/Law_of_Demeter . З цим погоджуєтесь чи ні, інша справа.
Майк Чемберлен

61

Основна мотивація така: Ви хочете мати можливість вирвати весь шар і замінити зовсім інший (переписаний), і НІКОЛИ НЕ БУДЕ ЗАМОВЛЕНО.

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

Наступний приклад - коли ви вириваєте середній шар і замінюєте інший середній шар. Розглянемо додаток, який використовує протокол, який працює над RS-232. Одного разу вам доведеться повністю змінити кодування протоколу, бо "щось інше змінилося". (Приклад: перехід від прямого кодування ASCII до кодування Ріда-Соломона потоків ASCII, тому що ви працювали по радіозв'язку від центру міста ЛА до Марини Дель Рей, і зараз ви працюєте по радіозв'язку з центру міста ЛА до зонда, що обертається навколо Європи , одна з лун Юпітера, і це посилання потребує набагато кращого виправлення помилок вперед.)

Єдиний спосіб зробити цю роботу, якщо кожен шар експортує відомий, визначений інтерфейс на шар вище, і очікує, що відомий визначений інтерфейс на шар нижче.

Тепер не зовсім так, що нижні шари знають НІЧЕ про верхні шари. Скоріше, те, що знає нижній шар, - це те, що шар безпосередньо над ним буде працювати саме відповідно до визначеного інтерфейсу. Він більше нічого не може знати, тому що за визначенням все, що не знаходиться у визначеному інтерфейсі, може змінюватися БЕЗ ПОПЕРЕДЖЕННЯ.

Шар RS-232 не знає, чи працює він ASCII, Reed-Solomon, Unicode (арабська кодова сторінка, японська кодова сторінка, кодова сторінка Rigellian Beta) чи що. Він просто знає, що отримує послідовність байтів і записує ці байти в порт. На наступному тижні він може отримати зовсім іншу послідовність байтів із чогось зовсім іншого. Йому все одно. Він просто переміщує байти.

Першим (і найкращим) варіантом багатошарового дизайну є класичний документ Дійкстри «Структура системи багатопрограмування THE» . Потрібно читати в цій справі.


Це корисно, і дякую за посилання. Я б хотів, щоб я міг вибрати дві відповіді як найкращу. Я в основному перекинув монету в голову і вибрав іншу, але я все-таки підтримав вашу.
Джейсон Світт

+1 для відмінних прикладів. Мені подобається пояснення, яке дав JRS
ViSu

@JasonSwett: Якби я перевернув монету, я б перегортав її, поки не призначив цю відповідь! ^^ +1 до Івана.
Олів'є Дулак

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

Дін Дінг Дінг !!! Я думаю, що слово, яке ви шукали, - це "розв'язка". Ось для чого хороші API. Визначення загальнодоступних інтерфейсів модуля, щоб його можна було використовувати універсально.
Еван Плейс

8

Тому що вищі рівні можуть змінитися.

Коли це відбувається, чи не через зміни вимог, нові користувачі, інша технологія, модульне (тобто однонаправлене шарувато) додаток потребує меншого обслуговування та легше адаптується до нових потреб.


4

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

Ось уривок:

Недоліки

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

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


4

ІМО, це дуже просто. Ви не можете повторно використовувати те, що постійно посилається на контекст, в якому він використовується.


4

Шари не повинні мати двосторонні залежності

Переваги шаруватої архітектури полягають у тому, що шари повинні бути використані незалежно:

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

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

Напрямок залежності повинен слідувати командному напрямку

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

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

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

Існують також інші способи збереження єдиного напрямку, наприклад:

  • продовження (передача лямбда або метод, який потрібно викликати і подія, до асинхронного методу)
  • підкласифікація (створити підклас в A батьківського класу в B, який потім вводиться в нижній шар, трохи як плагін)

3

Я хотів би додати свої два центи до того, що вже пояснили Метт Фенвік та Кіліан Фот.

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

Так, у шаруватій архітектурі нижні шари - це чорні ящики, на яких реалізуються шари абстракції, над якими будуються верхні шари. Якщо нижній шар (скажімо, шар B) може бачити деталі верхнього шару A, то B більше не є чорною скринькою: деталі його реалізації залежать від деяких деталей власного користувача, але ідея чорного поля полягає в тому, що його контент (його реалізація) не має значення для його користувача!


3

Задля розваги.

Придумайте піраміду вболівальників. Нижній ряд підтримує рядки над ними.

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

Якщо вона подивиться вгору, щоб побачити, як це роблять всі над нею, вона втратить рівновагу, внаслідок чого вся пачка впаде вниз.

Не дуже технічна, але це була аналогія, на яку я думав, що може допомогти.


3

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

Наприклад, припустимо, що A залежить від B. Оскільки від A нічого не залежить, розробники можуть вільно змінити вміст A у своїх серцях, не переживаючи, що вони можуть зламати щось, крім A. Однак якщо розробник хоче змінити B, то будь-яка зміна в B, що зроблено, може потенційно зламатися А. Ця проблема була частою проблемою в ранні комп'ютерні дні (думаю, структурована розробка), коли розробники виправляли б помилку в одній частині програми, і це призведе до появи помилок у, мабуть, абсолютно не пов'язаних частинах програми в інших місцях. Все через залежності.

Щоб продовжити приклад, тепер припустимо, що A залежить від B AND B залежить від A. IOW, кругової залежності. Тепер, будь-коли вноситься будь-яка зміна, вона може потенційно зламати інший модуль. Зміна в B все ще може зламати A, але тепер зміна A також може зламати B.

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

Це погіршується у великому проекті, де є багато розробників (наприклад, деякі, хто працює лише з шаром A, з деяким шаром B і з деяким шаром C). Оскільки стає ймовірним, що кожну зміну потрібно переглянути / обговорити з членами на інших шарах, щоб переконатися, що ваші зміни не порушуються або змушують переробити те, над чим вони працюють. Якщо ваші зміни вносять сильні зміни до інших, то вам доведеться переконати їх у тому, що вони повинні внести зміни, тому що вони не збираються брати на себе більше роботи тільки тому, що у вас є цей чудовий новий спосіб робити справи у своєму модулі. IOW, бюрократичний кошмар.

Але якщо обмеження залежності A залежить від B, B залежить від C, тоді лише людям шару C потрібно координувати свої зміни в обох командах. Шар B повинен лише координувати зміни з командою "Шар А" та шаром. Команда може робити все, що завгодно, оскільки їх код не впливає на шар B або C. Тому в ідеалі ви будете спроектувати свої шари так, що шар C дуже змінюється мало, шар B дещо змінюється, і шар A робить більшість змін.


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

1

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

Зауважте, що "нижні шари" - це насправді середні шари.

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

Операційна система не залежить від деталей у програмах, а також від драйверів пристроїв. Існує багато видів драйверів пристроїв одного типу і вони мають однакові рамки драйверів пристроїв. Іноді хакерам ядра доводиться вводити якусь особливу обробку справ у рамки заради певного обладнання або пристрою (останній приклад, який я натрапив: специфічний для PL2303 код в USB-послідовній основі Linux). Коли це трапляється, вони зазвичай ставлять коментарі про те, скільки це смокче і що потрібно зняти. Незважаючи на те, що ОС викликає функції драйверів, виклики проходять через гачки, завдяки яким драйвери виглядають однаково, тоді як, коли драйвери викликають ОС, вони часто використовують конкретні функції безпосередньо по імені.

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


Я щасливий, доки мені не доведеться турбуватися про встановлення конкретних напруг на конкретні штифти процесора :)
CVn

1

Розділення проблем та підходів до поділу / перемоги можуть бути ще одним поясненням цих питань. Відокремлення проблем викликає можливість переносимості, а в деяких більш складних архітектурах дає платформі незалежне масштабування та переваги у виконанні.

У цьому контексті, якщо ви думаєте про 5-ярусну архітектуру (клієнт, презентація, бізнес, інтеграція та ресурсний рівень), нижчий рівень архітектури не повинен знати про логіку та діловість вищих рівнів і навпаки. Я маю на увазі під нижчим рівнем як рівні інтеграції та ресурсів. Інтерфейси інтеграції баз даних, що надаються в інтеграції, а реальна база даних та веб-сервіси (сторонні постачальники даних) належать до рівня ресурсів. Отже, припустимо, що ви зміните свою базу даних MySQL на БД документа NoSQL, як MangoDB, з точки зору масштабованості чи будь-якого іншого.

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


1

Розширюючись на відповідь Кіліана Фота, цей напрям шарування відповідає напрямку, в якому людина досліджує систему.

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

Помилки, як правило, є невідповідністю між тим, що потрібно клієнту, і тим, що він отримує. Оскільки клієнт спілкується з системою через користувальницький інтерфейс і отримує результат через інтерфейс користувача (UI буквально означає «користувальницький інтерфейс»), про помилки повідомляється і з точки зору користувальницького інтерфейсу. Отже, як розробник, у вас немає великого вибору, крім того, щоб почати дивитися на інтерфейс користувача, щоб зрозуміти, що сталося.

Ось чому необхідне з'єднання шару зверху вниз. Тепер, чому ми не маємо зв’язків, що йдуть обома способами?

Ну, у вас є три сценарії, як ця помилка могла виникнути.

Це може виникнути в самому коді інтерфейсу, і тому там бути локалізованим. Це легко, потрібно просто знайти місце і виправити це.

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

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

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

Шарування - це інструмент, в першу чергу спрямований на розробників, які підтримують існуючу систему. Що ж, зв’язки між шарами також відображають це.


-1

Ще одна причина, яку я хотів би відзначити тут, - це повторне використання коду . У нас уже був приклад засобів масової інформації RS232, який заміняється, тому давайте підемо на крок далі ...

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

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

Скажімо, вам потрібно написати 5 драйверів для пристроїв Modbus. Один з них використовує Modbus TCP, два використовують Modbus на RS485, а решта переходять на RS232. Ви не збираєтесь повторно застосовувати Modbus 5 разів, оскільки ви пишете 5 драйверів. Крім того, ви не збираєтесь повторно реалізовувати Modbus 3 рази, тому що під вами є три різні фізичні шари.

Що ви робите, ви пишете TCP-доступ до медіа, доступ до медіа RS485 і, можливо, доступ до медіа RS232. Чи розумно знати, що в цьому моменті вище буде шар modbus? Напевно, ні. Наступний драйвер, який ви збираєтеся реалізувати, також може використовувати Ethernet, але використовувати HTTP-REST. Прикро, якби вам довелося повторно впроваджувати доступ до медіа-доступу до Ethernet, щоб спілкуватися через HTTP.

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

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


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