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の関係をコードに表すと以下のようになる。
#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. コード(コンポジション)
コンポジションの関係をコードで表すと以下の要になる。
#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の基本となるコンポジションと、集約について説明をしてみた。もし間違いがあれば、コメントで教えてください。