9日目のゴールとテーマ
9日目のテーマは
「クラス同士の“関係”を設計して、小さな本格アプリの形にする」 です。
ここまでであなたは、
1つのクラス(Task)を作り
オブジェクトを new して
ArrayList で複数管理し
カプセル化で「壊れにくいクラス」に育てる
ところまで来ました。
今日はここから一歩進んで、
クラスがクラスを「持つ」
クラスがクラスを「使う」
役割ごとにクラスを分ける
という、「クラス同士の関係」を意識した設計に入っていきます。
クラス同士の関係をイメージでつかむ
「〜が〜を持つ」「〜が〜を使う」
現実の世界を思い浮かべてみてください。
ユーザーがタスクを持つ
ショッピングカートが商品を持つ
アプリが設定を持つ
こういう「〜が〜を持つ」関係は、
プログラムの中では「クラスが別のクラスをフィールドとして持つ」ことで表現できます。
また、
ToDoアプリが TaskManager を使う
TaskManager が Task を管理する
といった「〜が〜を使う」関係も出てきます。
今日は、ToDoアプリを題材に、
Task(タスク1件)
TaskManager(タスク全体を管理する役)
という2つのクラスに分けて、
クラス同士の関係を体で覚えていきます。
まずはTaskクラスを再確認する
9日目で使うTaskの形
今日使う Task クラスは、8日目までで育てた「カプセル化版」をベースにします。
public class Task {
private String title;
private boolean done;
public Task(String title) {
setTitle(title);
this.done = false;
}
public String getTitle() {
return this.title;
}
public boolean isDone() {
return this.done;
}
public void setTitle(String title) {
if (title == null || title.isEmpty()) {
System.out.println("タイトルが空です。変更を無視します。");
return;
}
this.title = title;
}
public void complete() {
this.done = true;
}
public void printWithNumber(int number) {
String status = done ? "完了" : "未完了";
System.out.println(number + ": " + title + " / 状態: " + status);
}
}
JavaTask は「1件のタスク」を表すクラスです。
タイトルという情報を持ち
完了かどうかという状態を持ち
自分の表示の仕方を知っていて
自分を完了にすることができます。
今日は、この Task を「まとめて管理する役」を別クラスに切り出します。
TaskManagerクラスを作る
「タスクの一覧を持ち、操作をまとめて引き受ける役」
TaskManager は、「タスク全体を管理するクラス」です。
役割としては、
タスクの一覧を持つ
タスクを追加する
タスク一覧を表示する
タスクを完了にする
といった「タスクに関する操作」をまとめて引き受けます。
まずは骨組みから書いてみます。
import java.util.ArrayList;
public class TaskManager {
private ArrayList<Task> tasks;
public TaskManager() {
this.tasks = new ArrayList<>();
}
}
Javaここでのポイントをかみ砕きます。
private ArrayList<Task> tasks;
TaskManager は「Task のリスト」をフィールドとして持っています。
これは「TaskManager が Task を“持っている”」という関係です。
public TaskManager()
コンストラクタで、tasks を空の ArrayList で初期化しています。
TaskManager を new した瞬間から、「タスク一覧」が使える状態になります。
TaskManagerに「タスクを追加する」メソッドを持たせる
「Taskを作る責任」をどこに置くか
TaskManager に、タスク追加のメソッドを生やしてみます。
public class TaskManager {
private ArrayList<Task> tasks;
public TaskManager() {
this.tasks = new ArrayList<>();
}
public void addTask(String title) {
Task task = new Task(title);
tasks.add(task);
System.out.println("タスクを追加しました。");
}
}
Javaここでの重要ポイントは、
TaskManager が Task を new している
TaskManager が tasks に add している
というところです。
「タスクを作って、一覧に登録する」という一連の流れを、
TaskManager に任せています。
Main 側から見ると、こう書けます。
TaskManager manager = new TaskManager();
manager.addTask("Javaの勉強をする");
manager.addTask("買い物に行く");
JavaMain は「タスクを追加して」と頼むだけで、
「どうやって Task を作って、どこに保存するか」は TaskManager に隠されています。
TaskManagerに「一覧表示」を任せる
「どのように表示するか」も管理側の責任にする
次に、タスク一覧を表示するメソッドを追加します。
public void printTasks() {
if (tasks.isEmpty()) {
System.out.println("タスクは登録されていません。");
} else {
System.out.println("=== タスク一覧 ===");
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
task.printWithNumber(i + 1);
}
}
}
Javaここでのポイントは、
TaskManager が tasks をループで回している
表示自体は Task の printWithNumber に任せている
という分担です。
TaskManager
→ 「どのタスクを、どの順番で表示するか」を決める
Task
→ 「自分がどう表示されるか」を知っている
このように、「責任の境界線」を意識してクラスを分けると、
コードの見通しが一気によくなります。
TaskManagerに「完了処理」を任せる
「番号からTaskを探して、状態を変える」
次に、「何番のタスクを完了にするか」を TaskManager にやらせます。
public void completeTask(int number) {
int index = number - 1;
if (index < 0 || index >= tasks.size()) {
System.out.println("その番号のタスクは存在しません。");
return;
}
Task task = tasks.get(index);
if (task.isDone()) {
System.out.println("そのタスクはすでに完了しています。");
} else {
task.complete();
System.out.println("タスク「" + task.getTitle() + "」を完了にしました。");
}
}
Javaここでの重要ポイントを深掘りします。
int index = number - 1;
ユーザーには 1, 2, 3… という番号で見せていますが、
内部では 0, 1, 2… というインデックスで管理しています。
この変換を TaskManager の中でやっています。
if (index < 0 || index >= tasks.size())
範囲外チェックも TaskManager の責任です。
Main に「インデックスの正しさ」を意識させないようにしています。
task.isDone()
すでに完了している場合は、メッセージを出して何もしません。
「同じことを2回やったときの振る舞い」も、ここに閉じ込めています。
Mainクラスは「アプリ全体の流れ」だけに集中させる
TaskManagerを“相棒”として使う
ここまで来ると、Main クラスはかなりシンプルにできます。
import java.util.Scanner;
public class Main {
public static void printMenu() {
System.out.println("=== Taskアプリ(クラス分割版) ===");
System.out.println("1: タスクを追加する");
System.out.println("2: タスク一覧を表示する");
System.out.println("3: タスクを完了にする");
System.out.println("0: 終了する");
System.out.print("番号を選んでください: ");
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
TaskManager manager = new TaskManager();
while (true) {
printMenu();
int choice = scanner.nextInt();
scanner.nextLine(); // 改行を読み飛ばす
if (choice == 0) {
System.out.println("アプリを終了します。");
break;
} else if (choice == 1) {
System.out.print("追加するタスクを入力してください: ");
String title = scanner.nextLine();
manager.addTask(title);
} else if (choice == 2) {
manager.printTasks();
} else if (choice == 3) {
System.out.print("完了にするタスクの番号を入力してください: ");
int number = scanner.nextInt();
scanner.nextLine();
manager.completeTask(number);
} else {
System.out.println("その番号は無効です。");
}
System.out.println();
}
}
}
JavaMain がやっていることは、ほぼこれだけです。
メニューを表示する
ユーザーの入力を受け取る
TaskManager に「何をしてほしいか」を伝える
タスクの中身や、
タスク一覧の持ち方や、
完了処理の細かいルールは、
すべて TaskManager と Task に任せています。
クラス同士の関係を言葉で整理する
「〜が〜を持つ」「〜が〜を使う」をコードに対応させる
ここまでの設計を、言葉とコードで対応づけてみます。
TaskManager が Task を「持つ」
→ private ArrayList<Task> tasks;
→ 「TaskManager は Task のリストをフィールドとして持っている」
Main が TaskManager を「使う」
→ TaskManager manager = new TaskManager();
→ manager.addTask(...) など
→ 「Main は TaskManager に仕事を頼んでいる」
Task が「自分自身を表示する」
→ task.printWithNumber(...)
→ 「Task は自分の表示の仕方を知っている」
TaskManager が「どのTaskをどう扱うか」を決める
→ addTask, printTasks, completeTask
→ 「TaskManager はタスク全体の管理者」
こうやって、「役割」と「関係」を言葉で説明できるようになると、
クラス設計の筋が通ってきます。
責任の分担を意識することの意味
「1つのクラスに“何でもかんでも”詰め込まない」
もし TaskManager を作らずに、
全部を Main に書き続けていたらどうなるかを想像してみてください。
タスクのリストを持ち
タスクの追加ロジックを書き
一覧表示のロジックを書き
完了処理のロジックを書き
メニュー表示と入力処理も書き
Main が「何でも屋」になってしまいます。
そうなると、
どこを直せばいいか分かりにくい
同じようなコードがあちこちに出てくる
テストもしづらい
という状態になります。
TaskManager を作ることで、
タスクに関することは TaskManager
タスク1件の中身は Task
アプリ全体の流れは Main
という分担ができます。
この「責任の分担」を意識することが、
オブジェクト指向のクラス設計の核心の1つです。
9日目で一番大事な感覚
「クラスは“役割を持った登場人物”であり、“関係性”でアプリが形になる」
今日あなたに持ってほしい感覚はこれです。
クラスは、単に「データの入れ物」ではありません。
アプリの世界にいる「役割を持った登場人物」です。
Task は「タスク1件」という登場人物
TaskManager は「タスク全体の管理者」という登場人物
Main は「アプリ全体の司令塔」という登場人物
そして、その登場人物同士が、
〜が〜を持つ
〜が〜を使う
という関係でつながることで、
アプリ全体の形が立ち上がってきます。
9日目のまとめと、10日目への予告
今日やったことを短く整理すると、
Task(1件)と TaskManager(全体管理)という2つのクラスに役割を分けた
TaskManager が ArrayList<Task> を「持つ」ことで、タスク一覧を管理した
TaskManager に add / print / complete を持たせて、「タスク操作の窓口」にした
Main は「入力」と「TaskManagerへの指示」だけに集中できるようになった
クラス同士の「〜が〜を持つ」「〜が〜を使う」という関係を意識できた
10日目は、ここまでで作ってきたコンソールアプリの世界から、
「もう少し現実のアプリに近い設計」を意識して、
簡単な“ミニプロジェクト”としてまとめていきます。
そこまで行くと、「Javaでアプリを作る」という感覚がかなり自分のものになってきます。
