在一个简单答题系统的设计与实现(一)博文中列出了需求分析,与详细设计,现在按照之前的详细设计开始一步一步实现。
文件设计(数据层设计)
该系统由于需求场景限制,在数据层利用文件和文件的读写实现查询。具体思路如下图:
在该系统中用到的是以txt(文本文档)文件作为应用程序访问目标。既然是数据层,那么基础的CRUD肯定是要实现的。在该系统的实现中,我将其定位成工具类,功能是加载文件。我们在实现功能时,一定要想到这个功能不单单只能在我的程序中用到,别人如果需要用到这种功能时,拿我已经实现的功能直接可以用,即可复用性强。由于使用文件对数据要求繁琐复杂,必须将数据先转化成能够直接读取格式。
FileUtil.java 代码:
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import com.contest.model.Question;
/*读取文件内容*/
public class FileUtil {
/*将文件中的内容读取后按分割符分割并且加入列表返回*/
public static List<String[]> readTxtToParameter(String fileName){
File file = new File(fileName);
List<String[]> resultSet = new ArrayList<String []>();
String [] results = {};
try{
BufferedReader br = new BufferedReader(new FileReader(file));
String result = null;
while((result = br.readLine()) != null){
results = result.split("@");
resultSet.add(results);
}
}catch(Exception e){
System.err.println("读取文件"+fileName+"失败");
e.printStackTrace();
}
return resultSet;
}
//将指定txt文本写入路径名为fileName的文件中
public static void writeTxt(String fileName,String txt){
File writename = new File(fileName);
try {
writename.createNewFile();
BufferedWriter out = new BufferedWriter(new FileWriter(writename));
out.write(txt); // \r\n即为换行
out.flush(); // 把缓存区内容压入文件
out.close(); // 最后记得关闭文件
} catch (IOException e) {
e.printStackTrace();
}
}
//测试
public static void main(String args[]){
System.out.println(readTxtToParameter("data/test.txt").get(0)[3]);
writeTxt("data/test1.txt", "@20");
System.out.println(readTxtToParameter("data/test1.txt").get(0)[1]);
}
}
当然,这两个方法现在只是实现打标(之前说的回到上次答题位置功能)和加载,将题库先缓存在系统中。在实现至其他功能时,会在这里添加它该做的事情(方法)。
业务设计(逻辑设计)
在设计程序时,那么该应用程序一定是为了解决某种问题(或者能够使原本的事情能够更加便捷高效的完成)。解决问题肯定是有解决方法(解决步骤)的,这解决方法就是我们设计程序时所说的逻辑。在简单答题系统中,答题的逻辑就是:
- 答的是什么题
- 答的题是否正确
- 答错的题正确答案是什么?
当然,按照之前的UI设计,在该简单答题系统中还有其他的逻辑,比如:
- 上一题,下一题
- 生成试卷等
当我们能够理清自己设计系统中需要的逻辑时,在之后的代码设计中,能够减少一些”坏味道“(强耦合)。
在离用户最近的用户界面展示信息时,用户所看到的信息是怎样传过去的呢,是通过在数据库中直接查找的(或者是本系统中从文件中)?答案肯定是不对的,向上面那种做法,最终的代码会很不可读,创建连接和增删改查的语句遍布,让人难以理解其中的含义,更别说维护了。所以,我们会将业务信息抽象成一个类,即业务类,该类创建的对象即业务对象,也就是我们经常说的Bean。并且,将业务逻辑也整合成类,那么它创建的对象即业务逻辑对象,当然在本系统中不止一个业务逻辑类(每个业务逻辑干自己该干的事情)。下面是在本系统中按照之前的需求分析设计的业务类:
public class Question {
public static final String CHOOSE_QUESTION = "1";/*选择题*/
public static final String BLANK_QUESTION = "2";/*填空题*/
public static final String JUDGE_QUESTION = "3";/*判断题*/
public static final String JD_QUESTION = "4";/*简答题*/
private String questionId;/*问题编号*/
private String questionDesc;/*问题描述*/
private String answerDesc;/*答案描述*/
private String questionType;/*问题类型*/
private String remark;/*备注*/
public Question(String questionId,String questionDesc,String answerDesc,String questionType){
this.questionId = questionId;
this.questionDesc = questionDesc;
this.answerDesc = answerDesc;
this.questionType = questionType;
}
public String getQuestionId() {
return questionId;
}
public void setQuestionId(String questionId) {
this.questionId = questionId;
}
public String getQuestionDesc() {
return questionDesc;
}
public void setQuestionDesc(String questionDesc) {
this.questionDesc = questionDesc;
}
public String getAnswerDesc() {
return answerDesc;
}
public void setAnswerDesc(String answerDesc) {
this.answerDesc = answerDesc;
}
public String getQuestionType() {
return questionType;
}
public void setQuestionType(String questionType) {
this.questionType = questionType;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Question question = (Question) o;
if (questionDesc != null ? !questionDesc.equals(question.questionDesc) : question.questionDesc != null)
return false;
return questionType != null ? questionType.equals(question.questionType) : question.questionType == null;
}
@Override
public int hashCode() {
int result = questionDesc != null ? questionDesc.hashCode() : 0;
result = 31 * result + (questionType != null ? questionType.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Question [questionId=" + questionId + ", questionDesc=" + questionDesc + ", answerDesc=" + answerDesc
+ ", questionType=" + questionType + ", remark=" + remark + "]";
}
}
在该类中有三个值得注意的地方:
- 定义的常量
- equals与hashCode方法
- toString方法
为什么要这样做?
- 定义常量是为了将题的类型代码统一,在其他类中使用时直接调用,见名知意,且在之后维护时修改方便。
- 重写equals与hashCode方法,是为了在判断两个题是否同一题。以题的描述与题型判断。
- 重写toString方法是在本系统为了方便向文件中写入自定义格式化的对象。
在下一篇博客中,开始设计与实现各个部分业务逻辑。博主水平有限,希望与大家一起进步,欢迎大家指正。