Skip to content
Playground

演習: コンポジション

各問題ごとに .java ファイルを作り、Main.java で動作確認します。

挨拶メッセージを表示する Printer クラスを定義してください。次のとおり定義します。

  • フィールド: greeting演習 4-A で作った Greeting 型、private final
  • コンストラクターで Greeting のインスタンスを受け取り、フィールドに代入する
  • printHello(String name): greeting.sayHello(name) の戻り値を System.out.println で出力する

Main.javaGreeting のインスタンスを生成し、Printer のコンストラクターに渡してから printHello を呼びます。

Main.java
Greeting greeting = new Greeting();
Printer printer = new Printer(greeting);
printer.printHello("田中");

出力例:

こんにちは、田中さん
ヒント

本文 コンストラクター注入 と同じ書き方です。フィールドは private final Greeting greeting; の形で宣言し、コンストラクターで this.greeting = greeting; のように受け取った引数を代入します。

5-A で作った Printer に、挨拶した回数を数える機能を追加してください。Counter クラス(演習 1-A で作ったもの)を 2 つ目の依存として受け取ります。

  • フィールド: greeting(5-A と同じ)と counterCounter 型、private final
  • コンストラクターで GreetingCounter の両方を受け取る
  • printHello(String name): greeting.sayHello(name) の戻り値を出力したあと、counter.increment() で回数を増やす
  • printCount(): counter.getCount() で現在の回数を取得し、これまでに N 人に挨拶しました の形式で出力する
Main.java
Greeting greeting = new Greeting();
Counter counter = new Counter(0);
Printer printer = new Printer(greeting, counter);
printer.printHello("田中");
printer.printHello("鈴木");
printer.printHello("佐藤");
printer.printCount();

出力例:

こんにちは、田中さん
こんにちは、鈴木さん
こんにちは、佐藤さん
これまでに 3 人に挨拶しました
ヒント

本文 コンストラクター注入GradeReportService と同じ構造です。コンストラクターの引数を 2 つに増やし、両方のフィールドに代入します。

5-C. final フィールドのコンパイルエラー

Section titled “5-C. final フィールドのコンパイルエラー”

次のコードはコンパイルエラーになります。エラーの理由を説明し、コンパイルが通るように修正してください。

Printer.java
public class Printer {
private final Greeting greeting;
private final Counter counter;
public Printer(Greeting greeting) {
this.greeting = greeting;
}
public void printHello(String name) {
System.out.println(greeting.sayHello(name));
counter.increment();
}
}
ヒント

private final フィールドはコンストラクターでの初期化が必須です。本文 private final フィールド を参照してください。counter フィールドが宣言されているのにコンストラクターで初期化されていないため、コンパイラーがエラーを出します。

修正方法は 2 通りあります。どちらの形が Printer の役割に合うかを考えて選んでください。

ユーザー登録のサービスクラス UserRegistration を定義してください。EmailValidatorAgeChecker演習 4-D で作ったもの)の 2 つを依存に持ちます。

  • フィールド: emailValidatorEmailValidator 型、private final)、ageCheckerAgeChecker 型、private final
  • コンストラクターで EmailValidatorAgeChecker の両方を受け取る
  • register(String email, int age): 次の順で検査し、結果を出力する
    • emailValidator.isValidEmail(email)false なら メールアドレスが不正です: メール を出力して終了
    • ageChecker.isAdult(age)false なら 未成年は登録できません: 年齢 を出力して終了
    • 両方の検査を通ったら 登録しました: メール を出力

Main.java で 3 件のユーザー登録を試します。

Main.java
EmailValidator emailValidator = new EmailValidator();
AgeChecker ageChecker = new AgeChecker();
UserRegistration registration = new UserRegistration(emailValidator, ageChecker);
registration.register("tanaka@example.com", 25);
registration.register("invalid-email", 30);
registration.register("suzuki@example.com", 15);

出力例:

登録しました: tanaka@example.com
メールアドレスが不正です: invalid-email
未成年は登録できません: 15
ヒント

本文 GradeReportService の組み立てMain.java と同じパターンです。2 つの依存を new で生成してから、UserRegistration のコンストラクターに渡します。

register メソッドの中では、検査に失敗したら return; で早期に処理を抜けます。

サイコロを振るサービスクラス DiceRoller を定義してください。Java 標準ライブラリの java.util.Random を依存として受け取ります。

  • フィールド: randomjava.util.Random 型、private final
  • コンストラクターで Random のインスタンスを受け取る
  • roll(): random.nextInt(6) + 1 で 1〜6 の整数を返す
  • rollMultiple(int times): 指定回数サイコロを振り、各結果を System.out.println で 1 行ずつ出力する

Main.javaRandom のインスタンスを生成して DiceRoller に渡し、サイコロを 5 回振ります。

Main.java
import java.util.Random;
Random random = new Random();
DiceRoller dice = new DiceRoller(random);
dice.rollMultiple(5);

出力例(実行ごとに変わります):

3
1
6
4
2
ヒント

java.util.RandomnextInt(6) は 0 以上 6 未満(つまり 0〜5)の整数を返します。サイコロの 1〜6 にするために + 1 を加えます。rollMultiple の中で for 文を使い、roll()times 回呼び出します。

依存が標準ライブラリのクラスでも、コンストラクター注入の書き方は同じです。フィールドの型を Random にして、コンストラクターで受け取ります。

注文を受け付けるサービスクラス OrderService を定義してください。OrderCalculator演習 4-C で作ったもの)を依存に持ちます。

  • フィールド: calculatorOrderCalculator 型、private final
  • コンストラクターで OrderCalculator を受け取る
  • placeOrder(List<OrderItem> items): 次の処理を順に行う
    • calculator.calcTotal(items) で合計金額を計算
    • 注文を受け付けました。合計金額: 値円 を出力
Main.java
import java.util.ArrayList;
import java.util.List;
List<OrderItem> items = new ArrayList<>();
items.add(new OrderItem("りんご", 150, 3));
items.add(new OrderItem("みかん", 80, 5));
OrderCalculator calculator = new OrderCalculator();
OrderService service = new OrderService(calculator);
service.placeOrder(items);

出力例:

注文を受け付けました。合計金額: 850円
ヒント

OrderCalculator.calcTotal(items) は合計金額を int で返します。OrderService 自身は金額計算のロジックを持たず、OrderCalculator に処理を任せます。

5-G. 内部 new からコンストラクター注入への書き換え

Section titled “5-G. 内部 new からコンストラクター注入への書き換え”

次の GradeReportService は、依存しているクラスを report メソッドの中で new する書き方になっています。コンストラクター注入の書き方に変更してください。

GradeReportService.java(書き換え前)
import java.util.List;
public class GradeReportService {
public void report(List<Student> students) {
ScoreCalculator calculator = new ScoreCalculator();
GradeJudge judge = new GradeJudge();
int average = calculator.calcAverage(students);
System.out.println("平均: " + average);
for (Student s : students) {
System.out.println(s.getName() + ": " + judge.rank(s));
}
}
}

書き換えたあと、Main.javaScoreCalculatorGradeJudge のインスタンスを生成し、GradeReportService のコンストラクターに渡す形に変更します。

Main.java
import java.util.ArrayList;
import java.util.List;
List<Student> students = new ArrayList<>();
students.add(new Student("田中", 75));
students.add(new Student("鈴木", 50));
students.add(new Student("佐藤", 90));
ScoreCalculator calculator = new ScoreCalculator();
GradeJudge judge = new GradeJudge();
GradeReportService service = new GradeReportService(calculator, judge);
service.report(students);

出力例:

平均: 71
田中: B
鈴木: C
佐藤: A
ヒント

書き換え後の GradeReportService には次の 3 つを追加します。

  • private final ScoreCalculator calculator;private final GradeJudge judge; のフィールド
  • 2 つの依存を受け取るコンストラクター
  • report メソッドの中の new ScoreCalculator()new GradeJudge() を削除し、フィールドの calculatorjudge を使う

Counter演習 1-A で作ったもの)を依存として受け取る Visitor クラスを定義してください。

  • フィールド: counterCounter 型、private final
  • コンストラクターで Counter を受け取る
  • visit(String name): counter.increment() を呼んだあと、name さんが訪問しました(合計 N 人) の形式で出力する。N は counter.getCount() の値

Main.java で 2 つの Visitor インスタンスを作ります。最初は同じ Counter を共有して合計人数が積み上がることを確認し、その後で片方だけ別の Counter インスタンスに切り替えます。

Main.java
// 1 つの Counter を 2 つの Visitor で共有
Counter sharedCounter = new Counter(0);
Visitor entranceA = new Visitor(sharedCounter);
Visitor entranceB = new Visitor(sharedCounter);
entranceA.visit("田中");
entranceB.visit("鈴木");
entranceA.visit("佐藤");
// 別の Counter を渡して新しい Visitor を作る
Counter separateCounter = new Counter(0);
Visitor entranceC = new Visitor(separateCounter);
entranceC.visit("中村");
entranceA.visit("山田");

出力例:

田中さんが訪問しました(合計 1 人)
鈴木さんが訪問しました(合計 2 人)
佐藤さんが訪問しました(合計 3 人)
中村さんが訪問しました(合計 1 人)
山田さんが訪問しました(合計 4 人)

entranceAentranceBsharedCounter を共有しているので、田中・鈴木・佐藤・山田の 4 人の訪問が同じカウントに積み上がります。entranceC は別の separateCounter を持つので、中村の訪問は独立してカウントされます。

ヒント

同じ Counter インスタンスを 2 つの Visitor のコンストラクターに渡すと、両方の Visitor が同じカウントを共有します。別の Counter インスタンスを渡せば、独立したカウントを持つ Visitor になります。

コンストラクター注入の利点は、Visitor のコードを変えずに、渡す Counter のインスタンスを変えるだけで挙動が切り替わる点にあります。

Book演習 1-H で作ったもの)を整形して文字列を返すサービスクラス BookFormatter を定義してください。

  • フィールドなし
  • format(Book book): Book の情報を 『書名』(価格円、在庫 N 冊) の形式の文字列で返す
Main.java
Book book = new Book("こころ", 650, 3);
BookFormatter formatter = new BookFormatter();
System.out.println(formatter.format(book));

出力例:

『こころ』(650円、在庫 3 冊)
ヒント

BookFormatter 自身は依存を持ちません。引数で受け取った Book の getter(getTitle()getPrice()getStock())を呼び出して、文字列を組み立てて返します。

演習 1-I のヒントでは getTitle()getStock() だけ追加していたかもしれません。本問では getPrice() も必要なので、まだ定義していなければ追加してください。

書店の本を管理するサービスクラス BookService を定義してください。5-I で作った BookFormatter を依存に持ち、整形対象の Book のリストもフィールドに保持します。

  • フィールド: formatterBookFormatter 型、private final)、booksList<Book> 型、private final
  • コンストラクターで BookFormatterList<Book> の両方を受け取る
  • showAll(): 全部の本を formatter.format(book) で整形して順に出力する
  • showOutOfStock(): 在庫が 0 の本だけを formatter.format(book) で整形して出力する
Main.java
import java.util.ArrayList;
import java.util.List;
List<Book> books = new ArrayList<>();
books.add(new Book("こころ", 650, 3));
books.add(new Book("吾輩は猫である", 780, 0));
books.add(new Book("ノルウェイの森", 680, 5));
books.add(new Book("人間失格", 590, 0));
BookFormatter formatter = new BookFormatter();
BookService service = new BookService(formatter, books);
System.out.println("--- 全部の本 ---");
service.showAll();
System.out.println("--- 在庫切れ ---");
service.showOutOfStock();

出力例:

--- 全部の本 ---
『こころ』(650円、在庫 3 冊)
『吾輩は猫である』(780円、在庫 0 冊)
『ノルウェイの森』(680円、在庫 5 冊)
『人間失格』(590円、在庫 0 冊)
--- 在庫切れ ---
『吾輩は猫である』(780円、在庫 0 冊)
『人間失格』(590円、在庫 0 冊)
ヒント

BookServiceBookFormatter に整形処理を任せ、自分では出力の組み立てを書きません。showAllshowOutOfStock の中で、拡張 for 文 for (Book book : books) を使ってリストの各要素を順に処理します。

BookServiceBookFormatter を依存に持ち、BookFormatterBook を処理する、という 2 段階の構造です。コンストラクター注入の書き方は依存の数や種類が増えても変わりません。