4日目のゴール
4日目のテーマは
「同じ“追加・一覧・削除”を、より“変更に強い設計”に作り替えること」 です。
機能は増やしません。
でも、こういうところを意識してレベルを上げます。
クラス同士の依存関係を整理する
「Main に if 文を並べる」から一歩抜け出す
List と入力処理を“変化に耐えられる形”にする
今日は「明日、機能を増やしたくなっても怖くない ToDo アプリ」を目指します。
まず“今の問題”を言葉で見える化する
3日目までのコードの弱点
3日目までの ToDo アプリは、かなり良い形になっていましたが、
あえて弱点を挙げると、こんな感じです。
Main に if (“1”) / else if (“2”) / else if (“3”) が並んでいる
メニューを増やすたびに、Main の if 文が増えていく
「メニュー番号」と「処理」がベタ書きで結びついている
小さいうちは問題ありませんが、
「完了」「未完了だけ表示」「保存・読み込み」などを足したくなると、
Main がどんどん太っていきます。
4日目では、ここを一段整理します。
コマンドという“概念”を導入する
「メニュー番号」を“ただの文字列”で扱わない
今までは、こう書いていました。
String choice = input.readMenuChoice();
if ("0".equals(choice)) { ... }
else if ("1".equals(choice)) { ... }
else if ("2".equals(choice)) { ... }
else if ("3".equals(choice)) { ... }
Javaここでの問題は、
“1” が「追加」
“2” が「一覧」
“3” が「削除」
という対応が、コードを読まないと分からないことです。
そこで、「コマンド」という概念を導入します。
CommandType を enum で表現する
public enum CommandType {
ADD,
LIST,
DELETE,
EXIT,
UNKNOWN
}
Javaそして、「ユーザーの入力 → CommandType」に変換する役を作ります。
InputHelper に「コマンド解釈」を任せる
メニュー表示とコマンド変換を一箇所に集める
import java.util.Scanner;
public class InputHelper {
Scanner scanner;
InputHelper(Scanner scanner) {
this.scanner = scanner;
}
CommandType readCommand() {
System.out.println();
System.out.println("=== ミニToDoアプリ ===");
System.out.println("1: タスク追加");
System.out.println("2: タスク一覧");
System.out.println("3: タスク削除(ID指定)");
System.out.println("0: 終了");
System.out.print("番号を選んでください: ");
String line = scanner.nextLine();
if (line == null) {
return CommandType.UNKNOWN;
}
switch (line) {
case "0":
return CommandType.EXIT;
case "1":
return CommandType.ADD;
case "2":
return CommandType.LIST;
case "3":
return CommandType.DELETE;
default:
return CommandType.UNKNOWN;
}
}
String readLine(String prompt) {
System.out.print(prompt);
return scanner.nextLine();
}
Integer readInt(String prompt) {
System.out.print(prompt);
String line = scanner.nextLine();
if (line == null || line.isEmpty()) {
System.out.println("何も入力されていません。");
return null;
}
try {
return Integer.parseInt(line);
} catch (NumberFormatException e) {
System.out.println("整数で入力してください。");
return null;
}
}
}
Javaここでの重要ポイントは、
Main は「CommandType だけを見て分岐すればよくなる」
メニューの見た目や番号は InputHelper 側に閉じ込められる
ということです。
TaskManager を“純粋なロジックの塊”に近づける
4日目版 TaskManager
3日目の TaskManager を、ほぼそのまま使いますが、
「例外を外に投げる」形に少し寄せます。
import java.util.ArrayList;
public class TaskManager {
ArrayList<Task> tasks;
int nextId;
TaskManager() {
tasks = new ArrayList<>();
nextId = 1;
}
Task addTask(String title) {
Task task = new Task(nextId, title);
tasks.add(task);
nextId++;
return task;
}
boolean isEmpty() {
return tasks.isEmpty();
}
ArrayList<String> buildTaskLines() {
ArrayList<String> lines = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task t = tasks.get(i);
lines.add(t.toDisplayString(i));
}
return lines;
}
int findIndexById(int id) {
for (int i = 0; i < tasks.size(); i++) {
if (tasks.get(i).id == id) {
return i;
}
}
return -1;
}
Task removeTaskById(int id) {
int index = findIndexById(id);
if (index == -1) {
return null;
}
return tasks.remove(index);
}
}
Javaここでのポイントは、
TaskManager は「System.out.println」を一切呼ばない
「何が起きたか」は戻り値で伝える(Task / null / boolean など)
という形にしていることです。
これにより、
TaskManager は「テストしやすいロジックの塊」になります。
Main を“コマンド駆動”に書き換える
4日目版 Main の全体像
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
TaskManager manager = new TaskManager();
Scanner scanner = new Scanner(System.in);
InputHelper input = new InputHelper(scanner);
while (true) {
CommandType command = input.readCommand();
if (command == CommandType.EXIT) {
System.out.println("終了します。");
break;
}
switch (command) {
case ADD:
handleAddTask(manager, input);
break;
case LIST:
handleShowTasks(manager);
break;
case DELETE:
handleRemoveTask(manager, input);
break;
case UNKNOWN:
default:
System.out.println("不正な入力です。もう一度選んでください。");
break;
}
}
scanner.close();
}
static void handleAddTask(TaskManager manager, InputHelper input) {
String title = input.readLine("タスクのタイトルを入力してください: ");
if (title == null || title.isEmpty()) {
System.out.println("タイトルが空です。追加を中止します。");
return;
}
try {
Task task = manager.addTask(title);
System.out.println("タスクを追加しました: id=" + task.id + " / " + task.title);
} catch (IllegalArgumentException e) {
System.out.println("タスクの追加に失敗しました: " + e.getMessage());
}
}
static void handleShowTasks(TaskManager manager) {
System.out.println("=== タスク一覧 ===");
if (manager.isEmpty()) {
System.out.println("タスクはありません。");
return;
}
ArrayList<String> lines = manager.buildTaskLines();
for (String line : lines) {
System.out.println(line);
}
}
static void handleRemoveTask(TaskManager manager, InputHelper input) {
handleShowTasks(manager);
if (manager.isEmpty()) {
return;
}
Integer id = input.readInt("削除したいタスクのIDを入力してください: ");
if (id == null) {
System.out.println("削除を中止します。");
return;
}
Task removed = manager.removeTaskById(id);
if (removed == null) {
System.out.println("そのIDのタスクは存在しません: " + id);
} else {
System.out.println("タスクを削除しました: id=" + removed.id + " / " + removed.title);
}
}
}
Javaここでの重要ポイントは二つです。
Main は「CommandType に応じて、どの処理を呼ぶか」だけを決めている
各処理は、名前から役割が一目で分かる(handleAddTask など)
これで、メニューを増やしたくなったときも、
CommandType に値を足す
InputHelper の readCommand に分岐を足す
Main の switch に case を足す
対応する handleXXX メソッドを書く
という“型”で増やしていけます。
クラス設計・List・入力処理を“まとめて”見直す
クラス設計の観点から
4日目までで、役割はこう整理されています。
Task は「タスク1件のデータと表示形式」
TaskManager は「タスクの追加・検索・削除というロジック」
InputHelper は「ユーザー入力とメニュー表示」
CommandType は「アプリが理解するコマンドの種類」
Main は「コマンドに応じて処理を振り分ける司会進行」
この分け方ができていると、
どこかを変えたいときに「触るべき場所」がすぐ分かります。
List 管理の観点から
List に対してやっていることは、もう完全にパターン化しています。
可変長の集まりとして ArrayList<Task> を持つadd で追加size / isEmpty で状態確認for で 0〜size-1 を回して一覧を作る
ID で削除したいときは「ID → インデックス」を探してから remove(index)
このパターンは、
タスク以外の「ユーザー」「商品」「注文」などにも、そのまま使えます。
入力処理の観点から
入力処理も、こう整理されています。
入力はまず文字列として受け取る
「どう解釈するか」は InputHelper や Main が決める
メニュー番号 → CommandType という“意味のある型”に変換する
整数入力は readInt にまとめて、例外処理を一箇所に集める
これで、入力周りの変更(メニューの文言変更、番号変更など)も、
InputHelper を中心に見れば済むようになっています。
4日目で絶対に押さえてほしい本質
今日いちばん大事なのは、
「機能は増やしていないのに、“設計の質”を上げるだけでアプリの育ち方が変わる」
という感覚です。
CommandType という“意味のある型”を導入した
InputHelper に「メニュー表示+コマンド解釈」を任せた
TaskManager を「純粋なロジック」に近づけた
Main を「コマンド駆動の司会進行」にした
これらは全部、「今後の変更を楽にするための投資」です。
もし余力があれば、
この設計のまま「完了コマンド(DONE)」を自分で足してみてください。
CommandType に DONE を追加
InputHelper のメニューに「4: 完了」を追加
Main の switch に DONE を追加
TaskManager に「ID から Task を探して markDone する」メソッドを追加
ここまでできたとき、
あなたはもう「とりあえず動くコードを書く人」ではなく、
「育てられるコードを設計できる人」 に足を踏み入れています。

