
本文介绍一种面向对象、可维护性强的 java 问答系统设计方法:使用不可变 record 封装题目与选项,分离数据存储(questionsrepository)与交互逻辑(quiz),支持任意数量题目、随机顺序展示及多选反馈。
本文介绍一种面向对象、可维护性强的 java 问答系统设计方法:使用不可变 record 封装题目与选项,分离数据存储(questionsrepository)与交互逻辑(quiz),支持任意数量题目、随机顺序展示及多选反馈。
在开发测验类应用(如小考、知识竞赛或学习工具)时,若采用硬编码多个 new QuestionsLevelEasy(...) 实例并手动管理列表,不仅代码冗余、难以扩展,更违背了单一职责和开闭原则。理想的设计应满足:✅ 题目与答案结构清晰;✅ 支持 N 个选项(非仅单数字答案);✅ 数据加载与用户交互解耦;✅ 易于增删题目,无需修改业务逻辑。
✅ 推荐方案:用 record + 分层架构实现
1. 定义不可变题干模型(Question record)
Java 14+ 引入的 record 是表示只读数据载体的理想选择——自动生成构造器、getter、equals/hashCode/toString,且语义上明确表达“数据不可变”,天然规避并发与误修改风险:
public record Question(String ask, List<String> answers, int solutionOrdinal) {
// solutionOrdinal:正确答案在 answers 列表中的索引(从 1 开始,便于用户输入)
}⚠️ 注意:solutionOrdinal 使用 1-based 索引(即用户输入 1 表示第一个选项),而非编程常用的 0-based,显著提升用户体验与代码可读性。
2. 隔离数据源:QuestionsRepository
该类唯一职责是提供题目集合,当前以硬编码方式初始化,但未来可无缝替换为 CSV 解析、JSON 加载或数据库查询:
立即学习“Java免费学习笔记(深入)”;
完全公开源代码,并无任何许可限制 特别基于大型电子商务网站的系统开发 Microsoft SQL Server 2000后台数据库,充分应用了存储过程的巨大功效 基于类模块的扩展数据访问能力支持任何类型的大型数据库 加密用户登录信息(cookie) 易于安装的系统和应用功能 100%的asp.net的代码,没有COM,java或者其他的格式 完全基于MS建议的系统安全设计 最佳的应用程序,数据库
import java.util.List;
public class QuestionsRepository {
public List<Question> fetchQuestions() {
return List.of(
new Question("最佳编程语言?",
List.of("C", "C++", "Java"),
3 // Java 是第 3 个选项 → 用户输入 3 即正确
),
new Question("不存在的颜色是?",
List.of("品红(Magenta)", "红色", "蓝色"),
1 // 品红是加色模型中无对应波长的“非光谱色”
),
new Question("推荐的编程字体?",
List.of("Pragmata Pro", "JetBrains Mono", "Comic Sans MS"),
1
)
);
}
}✅ 优势:新增题目只需在 List.of(...) 中追加一行,零侵入现有逻辑。
3. 封装交互流程:Quiz 类
Quiz 不负责生成题目,只专注呈现、接收输入、校验反馈——这是典型的控制反转(IoC) 实践:
import java.util.*;
public class Quiz {
private final List<Question> questions;
public Quiz(List<Question> questions) {
// 创建可变副本并打乱顺序,再转为不可变视图
List<Question> shuffled = new ArrayList<>(questions);
Collections.shuffle(shuffled);
this.questions = List.copyOf(shuffled); // 安全防御:防止外部修改
}
public void present() {
Scanner scanner = new Scanner(System.in);
System.out.printf("? 本次测验共 %d 题,开始答题!\n", questions.size());
for (int i = 0; i < questions.size(); i++) {
Question q = questions.get(i);
System.out.printf("\n【第 %d 题】%s\n", i + 1, q.ask());
System.out.println("请选择答案(输入数字):");
// 清晰展示选项(1-indexed)
for (int j = 0; j < q.answers().size(); j++) {
System.out.printf("%d. %s\n", j + 1, q.answers().get(j));
}
int userChoice;
try {
userChoice = scanner.nextInt();
} catch (InputMismatchException e) {
System.out.println("❌ 输入错误!请输入数字。跳过本题。");
scanner.nextLine(); // 清除错误输入
continue;
}
if (userChoice == q.solutionOrdinal()) {
System.out.println("✅ 正确!");
} else {
System.out.printf("❌ 错误!正确答案是:%d. %s\n",
q.solutionOrdinal(),
q.answers().get(q.solutionOrdinal() - 1)
);
}
}
System.out.println("\n? 测验结束!");
}
}? 关键设计点:
- Collections.shuffle() 替代 Random.nextInt() 随机取题,避免重复抽取或遗漏;
- List.copyOf() 保证内部状态不可变,增强线程安全与封装性;
- 输入异常处理(如用户输字母)提升鲁棒性;
- 输出含题号、明确提示,符合 CLI 友好规范。
4. 组装运行:App 主类
public class App {
public static void main(String[] args) {
QuestionsRepository repo = new QuestionsRepository();
Quiz quiz = new Quiz(repo.fetchQuestions());
quiz.present();
}
}? 总结与进阶建议
| 维度 | 当前实现 | 后续可扩展方向 |
|---|---|---|
| 数据持久化 | 硬编码 List.of(...) | 改为读取 questions.json 或 SQLite |
| 难度分级 | 所有题目混入同一列表 | 在 Question 中增加 enum Level {EASY, MEDIUM, HARD} 字段 |
| 统计反馈 | 仅输出对错 | 记录答对数、耗时、错题重练列表 |
| UI 层 | 控制台(CLI) | 迁移至 JavaFX / Swing / Web(Spring Boot + Thymeleaf) |
? 核心启示:好的类设计不在于功能多,而在于职责纯、边界清、变化少。
将“题目是什么”(data)、“题目从哪来”(repository)、“怎么问用户”(quiz)三者彻底分离,你的代码才能真正支撑 30 题、300 题,甚至 3000 题的平滑演进。
现在,你已掌握构建专业级问答系统的核心范式——简洁、健壮、可生长。下一步,就是把它用在你的项目中,并持续迭代。









