C ++: ассоциация, агрегирование и композиция

Я начинаю изучать OOAD и с трудом могу найти C++ пример кода, который иллюстрирует, как Association, Aggregation и Composition реализуются программно. (Везде есть несколько сообщений, но они касаются C # или java). Я нашел один или два примера, но все они противоречат инструкциям моего инструктора, и я запутался.

Насколько я понимаю, в:

  • Связь: Foo имеет указатель на объект Bar в качестве элемента данных.
  • Агрегация: Foo имеет указатель на объект Bar, и данные Bar глубоко скопированы в этот указатель.
  • Состав: Foo имеет объект Bar в качестве члена данных.

И вот как я это реализовал:

class Bar
{
    Baz baz;
};

//ASSOCIATION (with Bar)
class Foo
{
    Bar* bar;
    void setBar(Bar* _bar)
    {
        bar = _bar;
    }
};

//AGGREGATION (with Bar)
class Foo
{
    Bar* bar;
    void setBar(Bar* _bar)
    {
        bar = new Bar;
        bar->baz = _bar->baz;
    }
};


//COMPOSTION (with Bar)
class Foo
{
    Bar bar;
    Foo(Baz baz)
    {
        bar.baz = baz;
    }
};

Это верно? Если нет, то как это сделать? Буду признателен, если вы также дадите мне ссылку на код из книги (чтобы я мог обсудить с моим инструктором)


person Talha5389    schedule 11.09.2014    source источник
comment
programmers.stackexchange.com - лучшее место для ответа на этот вопрос.   -  person R Sahu    schedule 11.09.2014
comment
Разница между агрегацией и ассоциацией невелика, и они, как правило, реализованы в C ++ аналогичным образом.   -  person Chris Drew    schedule 11.09.2014
comment
Помню, когда я учился в школе, я был совершенно сбит с толку той же терминологией. Затем, когда я попал в рабочую силу, единственная терминология, которая использовалась для обсуждения объектных отношений, - это «а», «имеет», «наследование» и «композиция». Тем не менее, я знаю, что в UML есть разные символы для агрегирования и композиции, но я никогда не видел, чтобы кто-нибудь делал различие между ними. Итак, найдите ответ, который даст вам правильные ответы в своем курсе, но не переживайте по этому поводу слишком сильно.   -  person jmrah    schedule 17.09.2014


Ответы (1)


Я собираюсь игнорировать агрегирование. Это не очень четко определенная концепция, и, на мой взгляд, она вызывает больше путаницы, чем того стоит. Композиции и ассоциации вполне достаточно, Крейг Ларман мне так сказал. Возможно, это не тот ответ, который искал ваш инструктор, но вряд ли он будет реализован на C ++ иначе, чем Association.

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

Состав

Самый простой способ реализовать композицию - использовать простую переменную-член Bar, как вы предложили. Единственное изменение, которое я хотел бы сделать, - это инициализировать bar в списке инициализаторов членов конструктора:

// COMPOSITION - with simple member variable
class Foo {
 private:
  Bar bar;
 public:   
  Foo(int baz) : bar(baz) {}
};

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

Также может быть причина реализовать композицию с помощью указателя. Например, Bar может быть полиморфным типом, и вы не знаете конкретный тип во время компиляции. Или, возможно, вы хотите переслать объявление Bar, чтобы минимизировать зависимости компиляции (см. идиому PIMPL). Или, возможно, кратность этого отношения составляет от 1 до 0..1, и вам необходимо иметь нулевое значение Bar. Конечно, поскольку это Композиция Foo должна владеть Bar, и в современном мире C ++ 11 / C ++ 14 мы предпочитаем использовать интеллектуальные указатели вместо владения необработанными указателями:

 // COMPOSITION - with unique_ptr
class Foo {
 private:
  std::unique_ptr<Bar> bar;
 public:   
  Foo(int baz) : bar(barFactory(baz)) {}
};

Я использовал здесь std::unique_ptr, потому что Foo является единственным владельцем Bar, но вы можете использовать std::shared_ptr, если другому объекту требуется от std::weak_ptr до Bar.

Ассоциация

Ассоциация обычно реализуется с помощью указателя, как вы это сделали:

// Association - with non-owning raw pointer
class Foo {
 private:
  Bar* bar;
 public:   
  void setBar(Bar* b) { bar = b; }
};

Конечно, вы должны быть уверены, что Bar будет активен, пока Foo его использует, иначе у вас будет зависший указатель. Если время жизни Bar менее ясно, то более подходящим может быть std::weak_ptr:

// Association - with weak pointer
class Foo {
 private:
  std::weak_ptr<Bar> bar;
 public:   
  void setBar(std::weak_ptr<Bar> b) { bar = std::move(b); }
  void useBar() {
    auto b = bar.lock();
    if (b)
      std::cout << b->baz << "\n";
  }
};

Теперь Foo может использовать Bar, не опасаясь остаться с висящим указателем:

 Foo foo;
 {
   auto bar = std::make_shared<Bar>(3);
   foo.setBar(bar);
   foo.useBar(); // ok
 }  
 foo.useBar(); // bar has gone but it is ok

В некоторых случаях, когда право собственности на Bar действительно неясно, ассоциация может быть реализована с использованием std::shared_ptr, но я думаю, что это должно быть крайней мерой.

Что касается вашей реализации агрегирования с глубокой копией указателя Bar. Я бы не сказал, что это типичная реализация, но, как я уже сказал, это зависит от ваших требований. Вам нужно убедиться, что вы вызываете delete для своего bar указателя-члена в Foo деструкторе, хотя в противном случае у вас есть утечка памяти (или используйте интеллектуальный указатель).

person Chris Drew    schedule 18.09.2014