关于考试系统的问题(二):如何设计题目类型
在设计题目存储结构的时候,通常需要一个良好的设计来兼容不同的题目需求,那么对应不同的需求,我们应该怎么设计呢?
只需要选择题
选择题的种类无非分为三种:
- 判断题
- 单选题
- 多选题
虽然看上去有三种题目类型,但是很明显可以察觉到,判断题其实只是只有两个特定选项的单选题而已。
此时题目的模型设计就可以大大简化:
Question
public class Question
{
public string Id { get; set; } = null!;
public QuestionType Type { get; set; } = QuestionType.SingleChoice;
public string Content { get; set; } = null!;
public List<QuestionAnswer> Answers { get; set; } = null!;
}
QuestionType
/// <summary>
/// 题目类型枚举
/// </summary>
public enum QuestionType
{
/// <summary>
/// 单选题
/// </summary>
SingleChoice = 0,
/// <summary>
/// 多选题
/// </summary>
MultipleChoice = 1
}
QuestionAnswer
public class QuestionAnswer
{
public string Id { get; set; } = null!;
public string QuestionId { get; set; } = null!;
public string Content { get; set; } = null!;
public bool IsCorrect { get; set; } = false;
}
以上代码展示了问题模型的结构,在客观题下这是最简化的设计。
如果有更多的需求,例如加入题目分类分组、区分题目难度、控制答案顺序,可以在题目模型和答案模型中添加对应的字段。
需要填空/简答题
填空题类型的加入让题目类型设计进入了下一个阶段,需要对题目答案进行不同的处理。我目前的理解是这样的:
首先先在题目类型中添加更多的题目类型。
QuestionType
public enum QuestionType
{
SingleChoice = 0,
MultipleChoice = 1,
FillIn = 2,
}
然后加入一个新的枚举类用于表示如何处理用户的答案是否正确。
QuestionType
public enum MatchStrategy
{
Exact = 0,
Contains = 1,
Regex = 2,
Manual = 3,
// Similarity = 4,
}
这里列出的几种方式只是一个例子,可以进行精确匹配、关键词匹配、正则表达式匹配、手动确认或者是使用文字嵌入比较用户答案和标准答案的相似度。
此时问题答案模型就要改变为以下格式: QuestionAnswer
public class QuestionAnswer
{
public string Id { get; set; } = null!;
public string QuestionId { get; set; } = null!;
public string Content { get; set; } = null!;
public bool IsCorrect { get; set; } = false;
public MatchStrategy MatchStrategy { get; set; } = MatchStrategy.Exact;
}
冗余一个字段MatchStrategy,这个字段只有填空题能使用到,字段IsCorrect只有判断题能使用到。
需要题目嵌套/材料题
材料题是下一个阶段会出现的题型,它的表现就是一个主题目负责存储材料内容,下面包含多个子题目,我是这样设计的:
1. 增加题目类型
QuestionType
public enum QuestionType
{
SingleChoice = 0,
MultipleChoice = 1,
FillIn = 2,
CaseStudy = 3,
}
2. 修改Questoin模型结构
Question
public class Question
{
public string Id { get; set; } = null!;
public QuestionType Type { get; set; } = QuestionType.SingleChoice;
public string Content { get; set; } = null!;
public string? ParentId { get; private set; } = null;
public List<QuestionAnswer> Answers { get; set; } = null!;
}
增加了一个字段ParentId,当Type的值为QuestionType.CaseStudy的时候会自动忽略ParentId,并且会忽略Answers字段,因为主题目只负责保存材料。当然这是因为我此处并没有设计无限嵌套,如果有无限嵌套的需求可以自定义规则。
放在最后
到这一步,题目模型的设计就基本上告一段落了。
设计出来的题型有单选、多选、填空、材料题。可以包含所有的选择题、填空/简答题,题目嵌套/材料题。市面上常用的题型也就是这些了,如果再使用标记语言例如markdown、Latex对题干和答案文本内容进行渲染,我相信效果是非常不错的。