Коли допустима кругова посилання на батьківський вказівник?


24

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

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

Я розумію, це, мабуть, не найкраща ідея загалом. З загального правила, як правило, справедливо сказати: "не роби цього!"

Однак мені цікаво, які умови існували б там, де потрібно було б зробити щось подібне. Це запитання тут та відповідні відповіді / коментарі пропонують навіть графікам не робити подібного.


1
Питання, яке ви зв'язали, здається досить вичерпним на цю тему.
Гонки легкості з Монікою

4
@LightnessRacesinOrbit "не роби цього" не дуже корисний, наскільки зрозуміти, чому .
Ендерленд

2
Я бачу набагато більше, ніж "не роби цього" там. Я бачу плюси і мінуси, які обговорюються багатьма експертами.
Гонки легкості з Монікою

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

2
Моє прагматичне правило - питання "Чи може дитина існувати без батька?". (Якщо ви розглядаєте XmlDocuments та його вузли: вузол не може існувати без контексту дерева документа. Це нісенітниця). Якщо відповідь ні, то в порядку двоспрямовані посилання: у вас є два об'єкти, які можуть існувати лише разом. Якщо об'єкти можуть існувати незалежно, я видаляю одне з цих двох посилань.
mikolai

Відповіді:


43

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

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

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

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

У більшості проектів OO посилання на інший об'єкт як член даних об'єкта передбачає право власності. Наприклад, припустимо, у нас є класи Автомобіль та двигун. Жоден із них не є дуже корисним сам по собі. Можна сказати, що ці об’єкти залежать один від одного: для виконання корисної роботи вони потребують присутності іншого. Але який "володіє" іншим? У цьому випадку ми б сказали, що автомобіль володіє двигуном, оскільки автомобіль - це «контейнер», в якому живуть усі автомобільні компоненти. І в OO, і в реальному дизайні автомобіль - це сума його деталей, і всі ці частини з'єднані між собою в рамках автомобіля. Мотор може мати посилання на Автомобіль, або він може мати посилання на TorqueConverter,

Круглі посилання можуть бути поганим дизайнерським запахом, але не обов’язково. При розумному використанні та коректному документі вони можуть полегшити використання структур даних.

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


16

У такій конструкції слід врахувати кілька аспектів:

  • структурні залежності
  • відносини власності (тобто склад проти інших видів асоціацій)
  • навігаційні потреби

Структурна залежність між класами:

Якщо ви спрямовані на повторне використання компонентних класів, вам слід уникати зайвої залежності та уникати таких закритих кругових структур.

Однак іноді два класи концептуально сильно взаємопов'язані. У цьому випадку уникнення залежності не є реальним варіантом. Приклад: дерево та його листя, або загалом композит та його компоненти .

Право власності на об'єкти:

Чи один об’єкт володіє іншим? Або інакше сказано: якщо один об'єкт знищений, чи буде знищений і другий?

Цю тему Сніговик глибоко розглядав, тому я не збираюся тут її зачіпати.

Потреби в навігації між об'єктами:

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

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

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

Звичайно, як вказували Гамма та ін, ви повинні забезпечити інваріанти кругових відносин. Це може бути складним, як показало питання ТА, на яке ви посилаєтесь. Але це ідеально керовано та безпечно.

Висновок

Потреба в навігації не повинна бути недооціненою. Недарма UML чітко звертався до цього в нотації моделювання. І так, є цілком справедлива ситуація, коли потрібні циркулярні посилання.

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


1
Ця відповідь IMHO сильно недооцінена. ОП запитала: "Враховуючи, що стосунки батько-дитина вже є, коли нормально це реалізувати за допомогою кругової посилання". Тож структура і «власність» (у сенсі, про який йде мова тут) вже зрозумілі. Це означає, що єдиними критеріями додавання посилань з тієї чи іншої сторони є питання щодо "навігаційних потреб" та "самостійного повторного використання дитини".
Док Браун

@DocBrown - Ви завжди можете покласти це щедро на це питання, як тільки воно стане прийнятним. :-)

1
@ GlenH7: це не дасть цій відповіді більше голосів, лише відповідь Сніговика (для якої я думаю, що це дещо пропускає суть питання).
Док Браун

1
@DocBrown - Але репз, людино, репз!

... і якщо ви додасте вибір видимості, наприклад захищений публічно-приватним, це дає 9 різних варіантів :)
mikalai

8

Зазвичай кругові посилання є дуже поганою ідеєю, оскільки означають кругові залежності. Ви, напевно, вже знаєте, чому кругові коефіцієнти погані, але для повноти, версія tl; dr - це те, що коли класи A і B обидва залежать один від одного, неможливо зрозуміти / виправити / оптимізувати / тощо або A, або B без також розуміння / виправлення / оптимізація / тощо іншого класу одночасно. Це швидко призводить до баз кодів, де ви нічого не можете змінити, не змінивши все.

Тим НЕ менше, це можливо мати циклічну посилання без створення поганих циклічних залежностей. Це працює до тих пір, поки посилання є строго необов’язковим у функціональному розумінні. Я маю на увазі, що ти можеш легко вийняти його з класів, і вони все одно працюватимуть, навіть якщо вони будуть працювати повільніше. Основний випадок використання, про який я знаю, для таких кругових посилань, що створюють незалежність, - це можливість швидкого обходу структур даних на основі вузла, таких як пов'язані списки, дерева та купи. Наприклад, в принципі будь-яка операція, яку ви можете виконати у подвійно пов'язаному списку, ви також можете виконати в окремо пов'язаному списку, просто трапляється кілька операцій (наприклад, переміщення назад через список), які мають набагато кращий O з подвійно пов'язаною версією.


6

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

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


5

[...] пропонує навіть графікам не робити подібного.

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

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


1
Так, цей приклад дійсно доречний. Саме такі речі я намагався описати у своєму аргументі про навігацію у складі: зворотний вказівник значно прискорює швидкість. Дякуємо за ваш цікавий анекдот та дуже чітку діаграму! +1
Крістоф

@Christophe Мені також подобається твій приклад! Я не був впевнений, чи справді я відповідав на це питання належним чином, оскільки, можливо, мова йшла більше про "кругову власність", ніж просто задні покажчики, які дозволяють нам пройти структуру даних вгору / назад. Але я в основному відповідав на ту останню частину питання "графіка".

2

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

Переваги:

  • Коли об'єкти можуть існувати в декількох списках одночасно, пам'ять для вузлів списку потрібно виділити та розмістити лише один раз.

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

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

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