2. Związki pomiędzy obiektami

2.3. Agregacja

O związku agregacji mówimy, jeśli obiekt agregowany jest częścią obiektu agregującego. Pełny opis zależności dla agregacji wygląda następująco: 

  • obiekt agregowany jest częścią obiektu agregującego
  • obiekt agregowany może należeć do więcej niż jednego obiektu agregującego jednocześnie
  • obiekt agregujący nie zarządza czasem życia obiektu agregowanego
  • obiekt agregowany zazwyczaj nie wie o istnieniu obiektu agregującego

W przeciwieństwie do asocjacji, agregacja jest relacją całość-część, gdzie części zawarte są w całości, i jest to relacja jednokierunkowa. 

Jednak, w przeciwieństwie do kompozycji, części mogą należeć do więcej niż jednego obiektu jednocześnie, a cały obiekt nie ponosi odpowiedzialności za istnienie i czas życia części. Kiedy tworzona jest obiekt agregujący, nie jest on odpowiedzialny za tworzenie obiektów agregowanych. Podobnie kiedy obiekt agregowany jest niszczony, nie niszczy swoich części (obiektów agregowanych). 

Na przykład, rozważmy relację między osobą a jej adresem zamieszkania. W tym przykładzie, dla uproszczenia, przyjmujemy, że każda osoba ma swój adres. Jednakże ten adres może należeć do więcej niż jednej osoby jednocześnie: na przykład do ciebie i twojego współlokatora lub partnera życiowego. Jednak ten adres nie jest zarządzany przez osobę - adres prawdopodobnie istniał wcześniej, zanim osoba się tam pojawiła, i będzie istnieć po tym, jak osoba odejdzie. Dodatkowo, osoba wie, pod jaki adresem mieszka, ale adresy nie wiedzą, kto tam mieszka. Dlatego ta relacja jest agregatem.

Inny przykład to  budowa typowych maszyn - np. samochodu. Samochód ma silnik, a silnik jest częścią samochodu. Jednocześnie jest jakiś właściciel. Ten właściciel ma samochód. Ale można też powiedzieć że ma silnik - więc silnik pozostaje w związku agregacji zarówno z samochodem, jak i z właścicielem. 
Dodatkowo - można wyjąć z samochodu silnik, można zdjąć koła z jednego auta i założyć je do drugiego.  

Jeszcze innym przykładem jest zależność Student - Grupa. Student należy do grupy - ale to nie grupa go stworzyła, i nie grupa go unicestwi ...  ;)

Z technicznego punktu widzenia - agregacja jest implementowana podobnie do asocjacji - znów mamy pole w klasie, znów jest to wskaźnik lub referencja. Nowością jest to, że nie implementuje się zależności odwrotnej. Poniżej przykład z wykorzystaniem inteligentnych wskaźników: 



#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <memory>

using namespace std::literals;

class CStudent
{
public:
    CStudent(std::string_view imie, std::string_view nazwisko) : m_imie{imie}, m_nazwisko{nazwisko} {  }

    std::string nazwa() const { return m_imie+" "s+m_nazwisko; }

private:
    std::string m_imie, m_nazwisko;
};

class CGrupa
{
public:
    CGrupa(std::string_view nazwa) : m_nazwa{nazwa} {  }

    void dodajStudenta(std::shared_ptr<CStudent> student) {
        m_studenci.push_back(student);
    }

    friend std::ostream& operator<<(std::ostream& out, const CGrupa& w);

    std::string nazwa() const { return m_nazwa; }

private:
    std::string m_nazwa;
    std::vector<std::shared_ptr<CStudent>> m_studenci;
};

std::ostream& operator<<(std::ostream& out, const CGrupa& w)
{
    if (w.m_studenci.empty()) {
        out << w.nazwa() << " nie ma jeszcze studentów";
        return out;
    }

    out << w.nazwa() << " składa się z: ";
    for (auto& student : w.m_studenci)
        out << student->nazwa() << ' ';

    return out;
}

int main()
{
    // Studenci powstają niezależnie od wykładowców, i w dowolnej kolejności
    std::shared_ptr<CStudent> janek{ new CStudent{"Janek", "Konieczny" }};
    std::shared_ptr<CStudent> basia{ new CStudent{ "Basia", "Niespodziana" }};
    std::shared_ptr<CStudent> stasio{  new CStudent{ "Stasio", "Przypadkowy" }};
    std::shared_ptr<CStudent> zosia{ new CStudent{ "Zosia", "Drążąca" }};

    CGrupa mistrzowie{"Mistrzowie"};
    CGrupa alfa{"Alfa"};

    mistrzowie.dodajStudenta(janek);
    mistrzowie.dodajStudenta(zosia);

    alfa.dodajStudenta(janek);
    alfa.dodajStudenta(basia);
    alfa.dodajStudenta(zosia);
    alfa.dodajStudenta(stasio);

    std::cout << "Grupy:\n";
    std::cout << mistrzowie << '\n';
    std::cout << alfa << '\n';

    return 0;
}