Task类应封装标题、描述、完成状态和创建时间,isCompleted设为private并提供complete()/uncomplete()方法;状态用TaskStatus枚举而非String;TaskManager统一管理集合操作并返回新集合;通过TaskRepository接口解耦存储,ID用UUID生成;CLI交互逻辑独立于业务模型。

用 Task 类封装核心状态和行为
待办事项最本质的属性是「标题、描述、完成状态、创建时间」,不是所有字段都该暴露给外部。把 isCompleted 设为 private,提供 complete() 和 uncomplete() 方法控制状态流转,避免外部直接赋值导致逻辑断裂。时间字段用 LocalDateTime 而非 Date,避免时区和可变性问题。
常见错误:把 status 设为 String(如 "done"/"pending"),后续加新状态或校验时容易出错。改用枚举更安全:
public enum TaskStatus { PENDING, COMPLETED, CANCELLED }
这样能天然防止非法值,且便于未来扩展状态机逻辑。
用 TaskManager 管理集合与业务规则
别让 main 方法或 UI 层直接操作 ArrayList。把增删查改、按状态筛选、按日期排序等逻辑全收进 TaskManager,对外只暴露语义清晰的方法,比如 addTask(Task task)、getPendingTasks()、findTasksByKeyword(String keyword)。
立即学习“Java免费学习笔记(深入)”;
注意点:
-
getCompletedTasks()应返回新集合(如new ArrayList(filtered)),而非原始引用,防止外部误修改内部状态 - 搜索方法默认区分大小写,若需模糊匹配,用
task.getTitle().toLowerCase().contains(keyword.toLowerCase()),但要注意空指针——先判task.getTitle() != null - 排序建议用
Collections.sort(tasks, Comparator.comparing(Task::getCreatedAt)),比手动写冒泡或选择排序更可靠
用接口解耦存储方式,方便后期替换
现在用内存列表存数据很轻量,但一旦要持久化到文件或数据库,硬编码的 ArrayList 就成了障碍。提前定义 TaskRepository 接口:
public interface TaskRepository {
void save(Task task);
List findAll();
void deleteById(String id);
}
先写个 InMemoryTaskRepository 实现它,后续换成 FileTaskRepository 或 JdbcTaskRepository 时,只要改一行 new 实例的代码,其他逻辑完全不用动。
容易被忽略的是 ID 生成策略:Task 构造时用 UUID.randomUUID().toString() 保证唯一性,别依赖数组下标或自增整数——后者在删除后会断层,也难跨进程共享。
命令行交互部分别侵入业务模型
用户输入解析(比如识别 "add Buy milk")、输出格式化(如用 System.out.printf 对齐字段)全放在单独的 TaskCLI 类里。它只调用 TaskManager 的方法,不碰 Task 的字段。
典型陷阱:
- 把
Scanner声明为静态变量,导致多线程或重复初始化时异常;应每次需要时新建,或作为实例变量在构造时传入 - 输入为空时没校验,
nextLine()返回空字符串,直接塞进Task构造器会存脏数据;加一句if (title.trim().isEmpty()) { System.out.println("Title cannot be empty"); continue; } - 未处理
NumberFormatException:用户输入"delete abc"时,Integer.parseInt("abc")会崩溃;必须包在try-catch里并提示友好错误
真正难的不是写完功能,而是让每层各司其职、边界清晰——模型不管怎么存,仓库不管怎么展示,命令行不管状态怎么流转。稍一越界,后面加导出 Excel 或 Web 接口时就全是补丁。











