Який хороший спосіб представити стосунки між багатьма класами?


10

Скажімо, у мене є два типи об’єктів, A і B. Взаємовідносини між ними багато-до-багатьох, але жоден з них не є власником іншого.

І A, і B випадки повинні знати про зв'язок; це не один спосіб.

Отже, ми можемо це зробити:

class A
{
    ...

    private: std::vector<B *> Bs;
}

class B
{
    private: std::vector<A *> As;
}

Моє запитання: де я розміщую функції для створення та руйнування з'єднань?

Чи повинен бути A :: Attach (B), який потім оновлює A :: Bs і B :: Як вектори?

Або це має бути B :: Attach (A), що здається однаково розумним.

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

Можливо, це має бути така функція:

CreateConnection(A, B);

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

Інше питання: якщо я часто стикаюся з цією проблемою / вимогою, чи можу я якось прийняти загальне рішення? Можливо, клас TwoWayConnection, з якого я можу отримати або використовувати в межах класів, які поділяють цей тип відносин?

Які є хороші способи впоратися з цією ситуацією ... Я знаю, як виправити ситуацію "багато хто з" D "досить добре, але цей складний складніше.

Редагувати: щоб зробити це більш чітким, це питання не пов’язане з проблемами власності. І A, і B належать ще одному об'єкту Z, а Z опікується всіма питаннями власності. Мене цікавить тільки те, як створити / видалити зв’язки між багатьма та багатьма між A і B.


Я створив би Z: Attach (A, B), тому, якщо Z володіє двома видами об'єктів, "почуває себе правильно", для нього можна створити з'єднання. У моєму (не дуже хорошому) прикладі Z буде сховищем для A і B. Я не можу побачити іншого способу без дотичного прикладу.
Мачадо

1
Якщо точніше, то A і B можуть мати різних власників. Можливо, A належить Z, тоді як B належить X, який, у свою чергу, належить Z. Як ви бачите, він намагається керувати посиланням в іншому місці. A і B повинні мати можливість швидко отримати доступ до списку пар, з якими він пов'язаний.
Дмитро Шуральов

Якщо вам цікаво справжні імена A і B в моїй ситуації, вони є Pointerі GestureRecognizer. Покажчики належать і керуються класом InputManager. GestureRecognizers належать екземплярам віджетів, які перебувають у власності екземпляра Screen, який належить екземпляру програми. Покажчики присвоюються GestureRecognizers, щоб вони могли надсилати їм вихідні вхідні дані, але GestureRecognizers потрібно знати, скільки покажчиків у них зараз пов’язано (розрізняти 1 палець проти 2 жести пальця тощо).
Дмитро Шуральов

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

Відповіді:


2

Один із способів - додати загальнодоступний Attach()метод, а також захищений AttachWithoutReciprocating()метод до кожного класу. Створіть Aта Bспільних друзів, щоб їхні Attach()методи могли викликати інших AttachWithoutReciprocating():

A::Attach(B &b) {
    Bs.push_back(&b);
    b.AttachWithoutReciprocating(*this);
}

A::AttachWithoutReciprocating(B &b) {
    Bs.push_back(&b);
}

Якщо ви реалізуєте подібні методи B, вам не доведеться пам'ятати, до якого класу звертатись Attach().

Я впевнений, що ти міг би перетворити таку поведінку в MutuallyAttachableклас, від якого Aі Bнаслідуєшся, тим самим уникнувши повторення та набрання бонусних балів у Судний день. Але навіть непрофілізований підхід “виконувати це в обох місцях” виконає роботу.


1
Це звучить саме як рішення, про яке я думав у самому розумі (тобто, щоб поставити Attach () і в А, і в B). Мені також дуже подобається більше DRY рішення (мені дуже не подобається дублювання коду) наявності класу MutualAttachable, який ви можете отримувати з будь-якого разу, коли така поведінка потрібна (я це передбачаю досить часто). Просто бачити те саме рішення, яке запропонував хтось інший, робить мене набагато впевненішим у цьому, дякую!
Дмитро Шуральов

Приймаючи це тому, що ІМО - це найкраще рішення, що пропонується поки що. Дякую! Я зараз буду реалізовувати цей клас MutualAttachable і повідомляти тут, якщо я зіткнуся з будь-якими непередбаченими проблемами (деякі труднощі можуть виникнути також через багаторазове успадкування, з яким я ще не маю багато досвіду).
Дмитро Шуральов

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

Ось MutuallyAttachableклас, який я написав для цього завдання, якщо хтось хоче його повторно використовувати: goo.gl/VY9RB (Pastebin) Редагувати: Цей код можна покращити, зробивши його шаблоновим класом.
Дмитро Шуральов

1
Я розмістив MutuallyAttachable<T, U>шаблон класу в Gist. gist.github.com/3308058
Дмитро Шуральов

5

Чи можливо, що саме відносини мають додаткові властивості?

Якщо так, то це повинен бути окремий клас.

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


Якщо це окремий клас, як би A знав пов'язані екземпляри B, а B знав пов'язані екземпляри A? У той момент, і А, і В повинні мати стосунки з С на багато, чи не так?
Дмитро Шуральов

1
І A, і B потребують посилання на колекцію екземплярів C; C служить тій самій цілі, що і «мостовий стіл» у реляційному моделюванні
Стівен А. Лоу

1

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

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

Іншим кандидатом на переміщення є об’єкт, який належить асоційованим об’єктам Zу вашому випадку.

Ще одним поширеним кандидатом для переміщення асоціації є код, який часто викликає методи на об'єкти Aабо Bоб'єкти. Наприклад, замість виклику a->foo(), який внутрішньо робить щось із усіма пов'язаними Bоб'єктами, він уже знає асоціації та викликає a->foo(b)кожен. Знову ж таки, якщо це називає кілька місць, його можна абстрагувати в один клас.

Багато разів об'єкти, які створюють, володіють і використовують асоціації, трапляються одним і тим же об'єктом. Це може зробити рефакторинг дуже легким.

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

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


0

Якби я реалізував це, я поставив би Attach як в A, так і в B. Всередині методу Attach я б подзвонив Attach на переданий об'єкт. Таким чином ви можете викликати або A.Attach (B), або B.Attach ( А). Мій синтаксис може бути неправильним, я не використовував c ++ протягом років:

class A {
  private: std::vector<B *> Bs;

  void Attach (B* obj) {
    // Only add obj if it's not in the vector
    if (std::find(Bs.begin(), Bs.end(), obj) == Bs.end()) {
      //Add obj to the vector
      Bs.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

class B {
  private: std::vector<A *> As;

  void Attach (A* obj) {
    // Only add obj if it's not in the vector
    if (std::find(As.begin(), As.end(), obj) == As.end()) {
      //Add obj to the vector
      As.push_back(obj);

      // Attach this class to obj
      obj->Attach(this);
    }
  }    
}

Альтернативним методом було б створення 3-го класу, який керує статичним списком пар AB.


Лише запитання щодо вашої альтернативної пропозиції щодо створення 3 класу C для управління списком пар AB: Як A та B могли знати своїх членів пари? Чи потрібно кожному A і B посилання на (одиночний) C, а потім попросити C знайти відповідні пари? B :: Foo () {std :: вектор <A *> As = C.GetAs (це); / * Зробіть щось із "..." /} Або ви передбачили щось інше? Тому що це здається жахливо заплутаним.
Дмитро Шуральов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.