複数の対象物が協調して仕事を行うような、 複雑な仕事を行うプログラムについて考えてみましょう。
複数のオブジェクトからなるプログラムについて、 あるオブジェクトとあるオブジェクトに「全体-部分」 の関係が成り立っている場合、 この関係のことを「包含 (composition)」や「集約 (aggregation)」と呼びます。 包含の関係は、あるオブジェクトが別のオブジェクトを保有し、 管理責任を持つという意味や、 あるオブジェクトの機能の一部を 別のオブジェクトに委譲するという意味があります。
電大では、成績による順位を決めるために GP (Grade Point) というポイントを計算しています。 GP は以下の計算式で計算することができます。
GP = Sを取った科目の合計単位数 * 5 + Aを取った科目の合計単位数 * 3 + Bを取った科目の合計単位数 * 2 + Cを取った科目の合計単位数 * 1
複数人の学生のGPの成績を処理し、 平均GP値を計算するプログラムを考えてみましょう。
このプログラムでは、学生のGPを計算するわけですから 1人の学生を表したGPを計算できるようなオブジェクトが必要です。 そして、複数名の学生のGPを集約して 平均を求めるオブジェクトも必要だと考えられます。
1人学生を表すクラスを TDUStudent とし、 複数の学生を集約するクラスを TDUStudentsRecord とします。
まず、クラス TDUStudent には、 以下のように、 これまでのような形で一人の学生に関する属性とメソッドを定義します。
class TDUStudent { String name; int numberOfS; int numberOfA; int numberOfB; int numberOfC; TDUStudent(String n, int s, int a, int b, int c) { name = n; numberOfS = s; numberOfA = a; numberOfB = b; numberOfC = c; } int getGradePoint() { return numberOfS * 5 + numberOfA * 3 + numberOfB * 2 + numberOfC * 1; } }
次に、クラス TDUStudent のオブジェクトを集約し、 複数の電大生のGPを処理するクラス TDUStudentsRecord を定義します。 クラス TDUStudentsRecord の定義は次のようになります。
class TDUStudentsRecord { TDUStudent[] students; TDUStudentsRecord() { students = new TDUStudent[3]; } void registerStudent(int id, TDUStudent s) { students[id] = s; } int averageGP() { int sum = 0; for(int i = 0; i < students.length; i++) sum = sum + students[i].getGradePoint(); return sum / students.length; } }
クラス TDUStudentsRecord は、 複数の TDUStudent オブジェクトを属性に持っています。 このようなクラスの関係が包含です。
複数の電大生のGPの平均を求めるメソッド averageGP を見てみましょう。 このメソッドでは、TDUStudent のGPを求めるメソッド getGradePointを下請けとして使っています。 GPの計算を、包含関係にあるクラス TDUStudent に委譲している、 というわけです。
このように、 クラスの機能の一部を包含関係にある別のクラスに委譲することができます。 包含によってクラス A がクラスB を取り込んだ状態を、 英語で Class A has a class B と言うことから、 has-a の関係と呼びます。
この例題で扱ったプログラムの全体を以下に示します。 (ファイル名: TDUManager.java)
class TDUManager { public static void main(String[] args) { TDUStudentsRecord record = new TDUStudentsRecord(); TDUStudent s1 = new TDUStudent("電大 メディ男", 2, 4, 3, 2); TDUStudent s2 = new TDUStudent("電大 デン次", 1, 5, 2, 2); TDUStudent s3 = new TDUStudent("電大 デン子", 3, 3, 1, 0); record.registerStudent(0, s1); record.registerStudent(1, s2); record.registerStudent(2, s3); System.out.println("GPの平均: " + record.averageGP()); } } class TDUStudentsRecord { TDUStudent[] students; TDUStudentsRecord() { students = new TDUStudent[3]; } void registerStudent(int id, TDUStudent s) { students[id] = s; } int averageGP() { int sum = 0; for(int i = 0; i < students.length; i++) sum = sum + students[i].getGradePoint(); return sum / students.length; } } class TDUStudent { String name; int numberOfS; int numberOfA; int numberOfB; int numberOfC; TDUStudent(String n, int s, int a, int b, int c) { name = n; numberOfS = s; numberOfA = a; numberOfB = b; numberOfC = c; } int getGradePoint() { return numberOfS * 5 + numberOfA * 3 + numberOfB * 2 + numberOfC * 1; } }
1曲の音楽を表すクラス Music を定義します。 これは以前の問題にもありました。
class Music { String name; String musician; Music(String n, String m) { name = n; musician = m; } String getName() { return name; } String getMusician() { return musician; } }
クラス Music のオブジェクトを集めて、 たくさんの音楽がつまったジュークボックスを定義してみます。 このジュークボックスで音楽が演奏される様子をプログラムで再現してみましょう。
クラス JukeBox の定義は次のようになります。
class JukeBox { Music[] songs; JukeBox() { songs = new Music[10]; } JukeBox(int numberOfSong) { songs = new Music[numberOfSong]; } void setMusic(int no, String title, String artist) { songs[no] = new Music(title, artist); } void play(int no) { System.out.println("Now playing: " + songs[no].getName() + " by " + songs[no].getMusician()); } }
クラス JukeBox は、たくさんの音楽を格納することができます。 このように、複数のオブジェクトを集約し管理するような クラスを作ることができます。
クラスを設計する際には、 1つのクラスの役割が大きくなりすぎないように注意する必要があります。 役割が大きすぎると感じたときには、 クラスを細かい対象物に分割し、包含を使うと良い場合があります。 また、将来変更の可能性のある部分は、 独立したクラスとして設計し、包含関係を定義することが良い場合があります。
次の例は社員の給料を計算するプログラムです。 給与体系は正社員とアルバイトで異なるため、 正社員の日給を計算するクラスと、 アルバイトの日給を計算するクラスを分けて設計することにします。
以下のプログラムは正社員の月給を計算するプログラムですが、 下線の Staff の部分だけを Arbeit に変更すれば、 アルバイトの月給を計算するプログラムに変えることができます。
class SalaryMan { public static void main(String[] args) { SalaryCalculation company = new SalaryCalculation(); company.setMember(new Staff("電大 メディ男")); // 9-18時で24日間働いたときの1ヶ月の給料を計算 int thisMonthsSalary = company.getMonthlySalary(9, 18, 24); System.out.println("今月の給料: " + thisMonthsSalary); } } class SalaryCalculation { Staff member; void setMember(Staff m) { member = m; } // 始業時間、就業時間、労働日数から1ヶ月の給料を計算 int getMonthlySalary(int from, int to, int days) { return member.getDairySalary(from, to) * days; } } // 正社員 class Staff { String name; Staff(String n) { name = n; } // 1日の給料は就業時間に関わらず 10000 円 int getDairySalary(int from, int to) { return 10000; } } // アルバイト class Albeit { String name; Albeit(String n) { name = n; } // 1日の給料は時給800円 int getDairySalary(int from, int to) { return (to - from) * 800; } }