1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

集約とコンポジション

Posted at

1. 要約

2つのクラスA, Bについて考える。

集約(Aggregation): クラスAがクラスBに集約された時、Aが削除されてもBは存在し続ける。

コンポジション(Composition):クラスAがクラスBに集約された時、集約されるBがAに対して依存性を持つ。Aが削除されると、Bも削除される。

2. 集約(Aggregation)

2.1. 集約とは?

集約はhas-a関係と呼ばれ、クラスAが状態の一部としてクラスBを含む関連の形を表している。(クラスAがBを所有している状態)クラスは独立して存在することができ、クラスAを削除しても、クラスBは存在し続けることができる。

2.2. 例(集約)

車の構成情報を保持するクラス設計を考える。この設計には、以下のクラスがある。

  • Car
  • Wheel

Carクラスは、Wheelクラスを所有しており、二つのクラスは関連している。Carクラスが削除されても、Wheelクラスはそれ自体で存在することができる。(車からタイヤを外しても、タイヤは存在し続けることができる)

2.3. コード(集約)

C++で、CarとWheelの関係をコードに表すと以下のようになる。

Aggregation.cpp
#include <iostream>
#include <vector>
#include <string>

class Wheel {
public:
    std::string brand;

    // コンストラクタ -> インスタンス生成時に呼ばれる
    Wheel(const std::string& b) : brand(b) {
        std::cout << "Wheel created: " << brand << std::endl;
    }

    // デストラクタ -> deleteやスコープ終了によりオブジェクトが破棄されると呼ばれる
    ~Wheel() {
        std::cout << "Wheel destroyed: " << brand << std::endl;
    }

    void rotate() const {
        std::cout << brand << " wheel is rotating!" << std::endl;
    }
};

class Car {
private:
    std::vector<Wheel*> wheels;  // 集約: ポインタで保持。Wheelとの関係は弱い。

public:
    // タイヤの追加
    void addWheel(Wheel* w) {
        wheels.push_back(w);
    }

    // 集約関係にあるタイヤを回転させる
    void testDrive() const {
        for (Wheel* w : wheels) {
            w->rotate();
        }
        std::cout << "\n" << std::endl;
    }

    // デストラクタ -> deleteやスコープ終了によりオブジェクトが破棄されると呼ばれる
    ~Car() {
        std::cout << "\n" << "---- Program End ----" << std::endl;
        std::cout << "Car destroyed." << std::endl;
    }
};

int main() {
    std::cout << "\n---- create wheels ----" << std::endl;
    // タイヤを生成する。ポインタで保持するので、deleteしないとメモリリークになる。
    Wheel* w1 = new Wheel("Michelin1");
    Wheel* w2 = new Wheel("Michelin2");

    std::cout << "\n---- add wheels to a car ----" << std::endl;
    // 車を生成する。ポインタで保持するので、deleteしないとメモリリークになる。
    Car* car = new Car();  
    car->addWheel(w1);
    car->addWheel(w2);

    std::cout << "\n---- test drive ----" << std::endl;
    car->testDrive();

    std::cout << "\n---- delete car (but not wheels) ----" << std::endl;
    delete car;  // Carだけを明示的に消す

    // 集約関係であるため、Carを削除してもWheelは生存しており、使用可能。
    std::cout << "\n---- wheels are still alive ----" << std::endl;
    w1->rotate();
    w2->rotate();

    // 明示的にdelete
    std::cout << "\n---- now delete wheels ----" << std::endl;
    delete w1;
    delete w2;

    return 0;
}

2.4. 実行結果(集約)

---- create wheels ----
Wheel created: Michelin1
Wheel created: Michelin2

---- add wheels to a car ----

---- test drive ----
Michelin1 wheel is rotating!
Michelin2 wheel is rotating!



---- delete car (but not wheels) ----

---- Program End ----
Car destroyed.

---- wheels are still alive ----
Michelin1 wheel is rotating!
Michelin2 wheel is rotating!

---- now delete wheels ----
Wheel destroyed: Michelin1
Wheel destroyed: Michelin2

CarとWheelは関連性があったが、Carを削除してもWheelは生きていて、Wheelのメソッドを実行することができる。

3. コンポジション(Composition)

3.1. コンポジションとは?

コンポジションは、集約の特殊な形とみることができ、集約される側が、集約する側に対して、依存性をもつ。このため、クラスAとBの間にコンポジションが適用されている場合には、クラスAを削除したとき、クラスBも同様に削除される。

コンポジションは、単純なクラスに依存性を持たせて、より複雑なクラスを作る場合などに使われる。これにより、保守がしやすいクリーンなコードを書くことができる。

3.2. 例(コンポジション)

図書館のソフトウェアシステムについて考える。このシステムでは、

  • Library
  • Book
  • Author

の3つから構成されている。Libraryクラスが削除された場合、Library内にあるBookも削除されるべきである。(=コンポジション)

ただし、Authorは複数のBookにまたがる可能性があるので、独立して存在する。(=集約)

3.3. コード(コンポジション)

コンポジションの関係をコードで表すと以下の要になる。

Composition.cpp
#include <iostream>
#include <string>
#include <vector>

class Author {
public:
    std::string name;
    Author(const std::string& n) : name(n) {}
};

class Book {
public:
    std::string title;
    Author* author;

    Book(const std::string& t, Author* a) : title(t), author(a) {}
    void print() const {
        std::cout << "BookInfo: \"" << title << "\" by " << author->name << std::endl;
    }
};

class Library {
private:
    std::vector<Book> books;

public:
    void addBook(const Book& book) {
        books.push_back(book);
    }

    Book* getFirstBook() {
        return &books[0];
    }
};

int main() {
    Book* outsideBookPtr = nullptr;

    Author* a = new Author("George Orwell");

    Library* lib = new Library();
    lib->addBook(Book("1984", a));

    outsideBookPtr = lib->getFirstBook();  // Bookの内部アドレスを保存
    outsideBookPtr->print();  // Bookの内容を表示

    delete lib;  // Libraryと中のBookが破棄される(ここで Book の寿命終了)

    std::cout << "\n---- trying to access deleted Book ----" << std::endl;
    outsideBookPtr->print();  // ここで未定義動作!
    std::cout << "---- end of access ----" << std::endl;
    delete a;
    return 0;
}

3.4. 実行結果(コンポジション)

BookInfo: "1984" by George Orwell

---- trying to access deleted Book ----
BookInfo: "秦}鍬・&鍬

Libraryを削除した後に、Book内のメソッドを呼び出すと、未定義動作となり(=BookはLibraryと同時に削除されている為)、期待した結果とならないことが分かる。

4. 終わりに

OOPの基本となるコンポジションと、集約について説明をしてみた。もし間違いがあれば、コメントで教えてください。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?