集約 vs コンポジション
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 48/87。
集約(aggregation)とコンポジション(composition)はどちらも「has-a」関係の形式ですが、1つの重要な側面において異なります。それは、所有権とライフサイクルの依存関係です。
コンポジション(合成)において、含まれるオブジェクトはコンテナから独立して存在することはできません。コンテナが破棄されると、その構成要素も同様に破棄されます。House とその Room オブジェクトを考えてみてください。部屋は家なしでは存在しません。
class House {
private Room[] rooms;
public House(int numRooms) {
rooms = new Room[numRooms];
for (int i = 0; i < numRooms; i++) {
rooms[i] = new Room(); // HouseがRoomを作成し、所有します
}
}
}集約(aggregation)では、含まれるオブジェクトは独立して存在できます。コンテナはそのオブジェクトを使用しますが、そのライフサイクルを制御することはありません。Teamと、そのPlayerオブジェクトを考えてみましょう。プレイヤーはチームがなくても存在します:
class Team {
private List<Player> players;
public Team() {
players = new ArrayList<>();
}
public void addPlayer(Player player) {
players.add(player); // チームは既存のプレイヤーを使用します
}
}主な違いは次のとおりです。コンポジションでは、コンテナがその構成要素を内部で作成します。集約では、オブジェクトは外部から渡されます。Team が削除されても、Player オブジェクトは存在し続けます。House が削除されると、その Room オブジェクトも消滅します。
チャレンジ
簡単集約(aggregation)とコンポジション(composition)の両方を実演する大学のコースシステムを構築しましょう。CourseがそのLectureオブジェクトを所有する(コンポジション)一方で、独立して存在するProfessorオブジェクトのみを参照する(集約)様子を確認します。
コードは4つのファイルに分けて構成します:
Professor.java: どのコースからも独立して存在できる教授を表すクラスを作成します。Professorは2つのプライベートフィールド、name(String) とdepartment(String) を持ちます。両方のフィールドを初期化するコンストラクタと、それぞれのゲッターメソッドを含めてください。toString()をオーバーライドして、"Prof. " + name + " (" + department + ")"を返すようにします。Lecture.java: コース内でのみ意味を持つ講義を表すクラスを作成します。Lectureは2つのプライベートフィールド、topic(String) とdurationMinutes(int) を持ちます。コンストラクタとゲッターを含めてください。toString()をオーバーライドして、"Lecture: " + topic + " (" + durationMinutes + " min)"を返すようにします。Course.java: ここで両方の関係を実演します!Courseは3つのプライベートフィールド、title(String)、professor(Professor - 集約)、およびlectures(Lectureオブジェクトの配列 - コンポジション) を持ちます。コンストラクタは、コースのタイトル、Professorオブジェクト(外部から渡される - 集約)、および講義数を受け取ります。コンストラクタ内でLecture配列を作成し、新しいLectureオブジェクトを格納します。インデックス
iの各講義について、トピックを"Topic " + (i + 1)、時間を45分として作成してください。この内部での作成がコンポジションを示しています。以下の形式の複数行のStringを返す
getInfo()メソッドを追加します:Course: [title] Instructor: [professor.toString()] Lectures: [number of lectures]また、各講義のtoString()をそれぞれの行に記述したStringを返す
listLectures()メソッドを追加します。Main.java: すべてをまとめます!教授の名前 (String)、教授の学部 (String)、コースのタイトル (String)、および講義数 (int) の4つの入力を受け取ります。まず、指定された名前と学部でProfessorを作成します。この教授は独立して存在します。次に、タイトル、教授(集約)、および講義数を渡してCourseを作成します(コースは内部で独自の講義を作成します - コンポジション)。
getInfo()の結果を出力し、次に空行を出力し、その後にlistLectures()の結果を出力します。
入力は、教授の名前、学部、コースのタイトル、講義数の順に4つ受け取ります。
主な違いに注目してください:Professorは外部で作成され、Courseに渡されます(集約 — 教授は他のコースを教えることも、このコースなしで存在することも可能です)。LectureはCourseのコンストラクタ内で作成されます(コンポジション — 講義はこのコースに排他的に属し、コースなしでは存在しません)。
チートシート
集約(aggregation)とコンポジション(composition)は、どちらも「has-a」関係ですが、所有権とライフサイクルの依存関係が異なります。
コンポジション: 含まれるオブジェクトは、コンテナ(保持する側)から独立して存在することはできません。コンテナが破棄されると、その構成要素も破棄されます。コンテナは内部でその構成要素を生成します。
class House {
private Room[] rooms;
public House(int numRooms) {
rooms = new Room[numRooms];
for (int i = 0; i < numRooms; i++) {
rooms[i] = new Room(); // HouseがRoomを生成し、所有する
}
}
}集約: 含まれるオブジェクトは独立して存在できます。コンテナはそのオブジェクトを使用しますが、そのライフサイクルを制御しません。オブジェクトは外部から渡されます。
class Team {
private List<Player> players;
public Team() {
players = new ArrayList<>();
}
public void addPlayer(Player player) {
players.add(player); // Teamは既存のPlayerを使用する
}
}主な違い: コンテナが削除されたとき、集約されたオブジェクトは引き続き存在しますが、コンポジションされたオブジェクトは破棄されます。
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 入力を読み込む
String professorName = scanner.nextLine();
String department = scanner.nextLine();
String courseTitle = scanner.nextLine();
int numberOfLectures = scanner.nextInt();
// TODO: Professorオブジェクトを作成する(独立して存在 - 集約)
// TODO: Professorを渡してCourseオブジェクトを作成する(集約)
// コースは内部で独自の講義を作成する(コンポジション)
// TODO: getInfo()の結果を出力する
// TODO: 空行を出力する
// TODO: listLectures()の結果を出力する
}
}このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造