前言
4~6次大作业题目的综合性较强,题目量大且给定的信息多,在完成题目要求之前做好题目需求分析必不可少,先从总体上把握题目大意,然后分模块实现各个功能。三次大作业重点考察面向对象编程的继承和多态,以及java正则表达式捕获信息,总体上说,这三次题目集的大作业的题目实用性强,与生活息息相关,突出面向对象编程的实际应用。
第四次大作业
答题判题程序-4大作业重点是熟练运用正则表达式捕获输入信息以及对信息的处理,在上一次的题目集的基础上增加了多选题与填空题,在答案匹配上新增了模糊匹配和选择匹配,不同类型的题目可作为题目类的子类,每个题目类中封装了各自的答案类和判题方法,接着对类和对应方法进行设计,在构建好类图的基础上把需求分成不同的模块,然后分模块完成题给要求。
设计分析
在Question类中有多个子类代表多种题型,因此在Paper类中增加对不同的题目的判题方法
class Paper { //试卷类
private String number; //试卷号
private Map<String,Integer> question; //储存题号和题目分数
int sum; //获得一张试卷的总分
List<Question> questionList;
List<Answer> answerList;
List<choicesQuestion> choicesQuestionList;
List<fillQuestion> fillQuestionList;
public int getSum(Paper p) { //获得一张试卷的总分
for(Integer score:p.question.values()){
sum+=score;
}
return sum;
}
//判断题目是否存在
public int isQuestionExist(String questionNum){ //传过来题目编号
for(Question q: questionList){
if(q.getNum()==Integer.parseInt(questionNum))
return 1;
}
for(choicesQuestion q: choicesQuestionList){
if(q.getNum()==Integer.parseInt(questionNum))
return 2;
}
for(fillQuestion q: fillQuestionList){
if(q.getNum()==Integer.parseInt(questionNum))
return 3;
}
return 0;
}
//回答是否正确(再检查)
public void Print1(int questionNum,Paper pp,int shunxu,Answer an){ //处理普通提
for(Question q: questionList){
List<DaAn> daAnList = an.getAnslist();
if(q.getNum()==questionNum) { //q就是这道题
for (DaAn daAn : daAnList) {
if (daAn.num.equals(shunxu + "")) {
System.out.println(q.getContent().trim()+"~"+daAn.daan+"~"+(q.matchStandAnswer(daAn.daan)?"true":"false"));
if(q.matchStandAnswer(daAn.daan)){
an.score[shunxu-1]=pp.question.get(questionNum+"");
}
return;
}
}
}
}
}
public void Print2(int questionNum,Paper pp,int shunxu,Answer an){ //处理选择题
for(choicesQuestion q: choicesQuestionList){
List<DaAn> daAnList = an.getAnslist();
if(q.getNum()==questionNum) { //q就是该题
for (DaAn daAn : daAnList) {
if (daAn.num.equals(shunxu + "")) {
String[] string = daAn.daan.split(" ");
switch(q.choiceMatch(string)){
case -1:
System.out.println(q.getContent().trim()+"~"+daAn.daan+"~"+"false");
break;
case 0:
System.out.println(q.getContent().trim()+"~"+daAn.daan+"~"+"partially correct");
an.score[shunxu-1]=(int)(pp.question.get(questionNum+"")/2);
break;
case 1:
System.out.println(q.getContent().trim()+"~"+daAn.daan+"~"+"true");
an.score[shunxu-1]=pp.question.get(questionNum+"");
break;
}
return;
}
}
}
}
}
public void Print3(int questionNum,Paper pp,int shunxu,Answer an) { //处理填空题
for (fillQuestion q : fillQuestionList) {
List<DaAn> daAnList = an.getAnslist();
if (q.getNum() == questionNum) {
for (DaAn daAn : daAnList) {
if (daAn.num.equals(shunxu + "")) {
switch (q.fillMatch(daAn.daan)) {
case -1:
System.out.println(q.getContent().trim() + "~" + daAn.daan + "~" + "false");
break;
case 0:
System.out.println(q.getContent().trim() + "~" + daAn.daan + "~" + "partially correct");
an.score[shunxu - 1] = (int) (pp.question.get(questionNum + "") / 2);
break;
case 1:
System.out.println(q.getContent().trim() + "~" + daAn.daan + "~" + "true");
an.score[shunxu - 1] = pp.question.get(questionNum + "");
break;
}
return;
}
}
}
}
}
public void addQuestion(String questionNum, int score){
question.put(questionNum,score);
}
public Paper(){ //
this.question = new LinkedHashMap<>();
}
public void setQuestionList(List<Question> questionList) {
this.questionList = questionList;
}
public List<Answer> getAnswerList() {
return answerList;
}
public void setAnswerList(List<Answer> answerList) {
this.answerList = answerList;
}
public List<choicesQuestion> getChoicesQuestionList() {
return choicesQuestionList;
}
public void setChoicesQuestionList(List<choicesQuestion> choicesQuestionList) {
this.choicesQuestionList = choicesQuestionList;
}
public List<fillQuestion> getFillQuestionList() {
return fillQuestionList;
}
public void setFillQuestionList(List<fillQuestion> fillQuestionList) {
this.fillQuestionList = fillQuestionList;
}
public Paper(String number, Map<String, Integer> question, List<Question> questionList, List<Answer> answerList) {
this.number = number;
this.question = question;
this.questionList = questionList;
this.answerList = answerList;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Map<String, Integer> getQuestions() {
return question;
}
public void setQuestions(Map<String, Integer> questions) {
this.question = questions;
}
}
题目新增的多选题和填空题作为Question类的子类创建,继承父类Question类的通用信息,属性及方法源码如下:
class choicesQuestion extends Question {
String[] choicesStandardAnswer;
String[] answers;
public choicesQuestion(int num, String content, boolean isValid, String[] choicesStandardAnswer) {
this.num = num;
this.content = content;
this.isValid = isValid;
this.choicesStandardAnswer = choicesStandardAnswer;
}
public String[] getAnswers() {
return answers;
}
public void setAnswers(String[] answers) {
this.answers = answers;
}
public int choiceMatch(String[] answer) { //返回-1全错,返回0对一半,返回1全对
int count = 0, flag = 0;
for (int i = 0; i < answer.length; i++) { //取一个自己的答案
flag = 0;
for (int j = 0; j < choicesStandardAnswer.length; j++) { //遍历标准答案数组
if (answer[i].equals(choicesStandardAnswer[j])) {
flag = 1;
count++;
break;
}
}
if (flag == 0)
return -1; //存在错误答案
}
if (count == choicesStandardAnswer.length)
return 1;
else
return 0;
}
}
class fillQuestion extends Question{
public fillQuestion(int num, String content, String standardAnswer, boolean isValid) {
super(num, content, standardAnswer, isValid);
}
public int fillMatch(String answer){ //返回1是全队,返回0半对,返-1全错
if(answer.trim().equals(standardAnswer.trim()))
return 1;
if(standardAnswer.trim().contains(answer.trim()))
return 0;
else
return -1;
}
}
当把所有类全部设计完毕后,考虑类与类之间的逻辑关系,将不同的类关联起来,画出的PowerDesigner类图如下
该题主要考虑新增的多选题和填空题的判断格式以及正确判题和计分方法,重点在于用正则表达式判断题目格式是否正确和用group分组捕获输入信息。
踩坑心得
该题目的格式测试点大多在对空格的处理,可以是空字符也可以是一个到多个空格,还有各种信息的乱序处理,由于没有考虑多学生多信息的乱序处理导致测试点错误
这里的错误主要是如果有多学生且不同学生名字前面有一至多个空格,那么在读取并创建学生对象时要把所有的空格全部忽略但是在输出得分信息时要把人名前面的空格输出,判断时没空格,输出时有空格,在测试时要多用测试用例对代码进行测试
改进建议
尽量把Main中的代码在类中用方法去完成,要记住类设计原则的单一设计原则,一个方法尽量完成一个功能,避免方法功能过于复杂。这样能够提高代码的可读性和可维护性。
另外,建议对代码进行注释,特别是一些复杂的逻辑或者算法部分,能够帮助他人更快地理解你的代码。同时,要注意命名规范,让变量名和方法名能够清晰表达其用途,提高代码的可读性。
第五次大作业
设计与分析
家居强电电路模拟程序-1大作业内容丰富,涉及到的设备多,并且有串联,并联,短路,通路,断路等多种情况需要考虑,由于本次题目只有一条串联通路,逻辑比较简单,根据给定设计建议
将整个电路分成电路设备类包括如开关,调速器,白炽灯,电扇等不同的设备,提供各自的属性和方法,还有电路控制类比如串联或并联等不同的电路结构,在将所有的类和方法创建完毕后要读取输入信息,用spilt分割处理,从中读取出不同的设备并放入容器中,由于该题目只考虑一条串联电路,那么可以创建一个设备类的父类Device集合用于存放该条串联电路上的所有电器,最后在list中按照设备类型和型号排序
排序方法
这里的排序较为巧妙,由于题目要求按照开关、分档调速器、连续调速器、白炽灯、日光灯等顺序按照类型排序,同一类型的则按照设备编号按从小到大的方式排序,同一类型的较为简单,直接按设备的名字排序即符合要求,不同类型的设备可以给不同类型的设备给定一个新的type属性,开关最小即为1,分档调速器次之即为2,由此依次进行赋值,用list的sot的方法中创建比较器重写CompareTo方法,先要判断两个设备的类型是否相同,如不相同则按照Type从小到大排,如果类型相同,则按照两设备的名字排序
排序代码源码如下:
devices.sort(new Comparator<Device>() {
@Override
public int compare(Device o1, Device o2) {
if (!(o1.getTypeValue() == o2.getTypeValue())) {
return o1.getTypeValue() - o2.getTypeValue();
} else {
return o1.getName().compareTo(o2.getName());
}
}
});
以下是对源码的具体解析:
public void input() {
Scanner input = new Scanner(System.in);
while (input.hasNext()) {
String str = input.nextLine();
if (str.equals("end"))
break;
if (str.startsWith("#K")) { //开关
String s = str.replace("#", "");
for (Device device : devices) {
if ( device.name.equals(s)) {
((Switcher) device).change();
break;
}
}
}
if (str.startsWith("#F")) {
Pattern pattern = Pattern.compile("#(F\\d+)([+-]$)");
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
String name = matcher.group(1); //分档的名字
String flag = matcher.group(2); //挡位变化
for (Object device : devices) {
if ( ((Speeder) device).name.equals(name)) {
if (flag.equals("+")) {
((Speeder) device).addSpeed();
} else if (flag.equals("-")) {
((Speeder) device).reduceSpeed();
}
break;
}
}
}
}
if (str.startsWith("#L")) { //连续器
Pattern pattern = Pattern.compile("#(L\\d+):(.+$)");
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
String name = matcher.group(1);
String rate = matcher.group(2);
for (Object device : devices) {
if ( ((Continuer) device).name.equals(name)) {
((Continuer) device).rate = Double.parseDouble(rate);
break;
}
}
}
}
if (str.startsWith("[")) { //[VCC K1-1]
String s1 = str.replace("[", "");
String s2 = s1.replace("]", "");
String[] s3 = s2.split(" "); //VCC K1-1 s3
Deal0(s3[0]);
Deal1(s3[1]);
}
}
}
由给出的题目输入样例看,读入的信息有两种,一种是处理信息,即对设备的管理,如对分档调速器挡位的调整和对连续调速器比率的调整,另一种是对连接信息的处理,即电路上的不同设备的连接情况,读取到不同设备后再以此创建出电路,Deal0和Deal1方法则是读取到不同的设备,但是Deal0为第一次读取新设备需要创建具体对象并且连接设备的某个引脚,Deal1方法则是第二次读到设备,其作用是连接设备的另一个引脚,此时不应该又一次创建该对象了,不同类型设备的判断则用startsWith方法处理,在读取完毕且创建好对象后及那个设备放入list集合中即可
几点注意事项:
1.由于该类题目的迭代版本多,因此在设计时要考虑程序的可扩展性以及代码的复用性。此次题目只考虑的是一条串联电路无并联也不考虑串联里边套串联电路,还忽略用电器的电阻,在设计时如果仅仅考虑一道题则下次迭代代码很多不再适用,这样代码无法再次复用,因此在设计时要考虑到多种情况以便能增加代码的延展性
2.该题目的所有设备都有两个引脚需要重点考虑,如果设备的输入引脚或输出引脚没有连接,则该设备实际上并没有连接在电路中,比如如果开关的输出引脚没有连接,那么开关的上一个设备的输出引脚和开关下一个设别的输入引脚相连,本质上开关并没有起到作用,因此在电路中开关不该存在所以开关的闭合与否对电路不产生任何影响。
3.该题目中开关是重点,开关的两个引脚是否正确连接以及开关是否闭合都影响开关在电路中起的作用,由于整个电路只有一条串联电路,那么可以把电压看成是静态变量,在读取到调速器对电压的处理后要在输出结果前改变电压值
开关设备源码如下:
class Switcher extends Device { //开关
int flag = 0; //开关状态
@Override
public void print() {
if(!check()){
return;
}
if (flag == 0) {
System.out.println("@" + getName() + ":turned on");
value = 0;
} else {
System.out.println("@" + getName() + ":closed");
}
}
public boolean check(){
if(!Device2){
return false;
}
else
return true;
}
public void change() {
flag = 1 - flag;
}
}
踩坑心得
写完代码后要对代码进行边界测试,不同的用电器的分压方法是否准确,如果有用电器的输出方法错误则输出结果一定错误。
由于风扇的输出结果方法计算有误导致与风扇有关的所有测试点全不过,因此对各种设备的输出结果要重点关注,尤其是设备的最大分压的临界值。
其次在读取信息时由于不知道电器的类型可以用if判断以创建对象,但是输出是在排序好后的集合里按顺序依次输出,此时在集合中取出的设备类型是确定的,不再需要用if判断要输出了,以及灵活利用多态和方法重写的优势,一开始并没有想到而是在Output方法中用if判断输出,这样太麻烦且不具有代表性,加入该串联电路有5个设备,由于共有6个设备则每一个设备都需要用if来判断6次,总共需要判断30次,这样写的话代码过于重复且大部分是无效代码,可维护性也不高,因此可以考虑将输出方法放在具体的设备类中,通过分类的引用来调用子类的方法,实现多态。
public void output() {
double value = 220;
//如果devices.get(1)是开关
if (devices.get(1).getClass().getName().equals("Switcher")) {
Switcher switcher = (Switcher) devices.get(1);
if (switcher.flag == 0) {
System.out.println("@" + switcher.getName() + ":turned on"); //@K1:turned on
value = 0;
} else {
System.out.println("@" + switcher.getName() + ":closed");
value = 220;
}
}
if (devices.get(1).getClass().getName().equals("Speeder")) { //@F1:2
Speeder speeder = (Speeder) devices.get(1);
System.out.println("@" + speeder.getName() + ":" + speeder.speed);
if (speeder.speed == 0) {
value = 0;
} else if (speeder.speed == 1) {
value = (value * 0.3);
} else if (speeder.speed == 2) {
value = (value * 0.6);
} else if (speeder.speed == 3) {
value = (value * 0.9);
}
}
if (devices.get(1).getClass().getName().equals("Continuer")) { //#L1:0.68
Continuer continuer = (Continuer) devices.get(1);
System.out.println("@" + continuer.getName() + ":" + String.format("%.2f", continuer.rate));
value = (value * continuer.rate);
}
if (devices.get(2).getClass().getName().equals("WhiteLight")) { //@B2:200
WhiteLight whiteLight = (WhiteLight) devices.get(2);
System.out.println("@" + whiteLight.getName() + ":" + whiteLight.getLight(value));
}
if (devices.get(2).getClass().getName().equals("SunLight")){
SunLight sunLight = (SunLight) devices.get(2);
System.out.println("@" + sunLight.getName() + ":" + sunLight.getLight(value));
}
if(devices.get(2).getClass().getName().equals("Fan")){
Fan fan = (Fan) devices.get(2);
System.out.println("@" + fan.getName() + ":" + fan.getSpeed(value));
}
}
该代码的复用性太差且工作量太大,容易出错,修改后代码:
for (Object device : devices) {
((Device) device).print();
}
父类的Print方法在不同的设备子类中全部重写,在调用时只会调用子类的Print方法,这样修改可以适用于各种设备的混合连接,代码更简洁且复用性和拓展性大大增强。
持续改进意见
ArrayList<Device> devices = new ArrayList<Device>();
这里仅用一个list储存该条串联电路上所有设备,只适用于此次题目的特点,当题目中存在并联电路中代码要大量删减,建议将设备分组,分别用不同的列表存储,并在需要时进行合并操作。这样可以提高代码的可读性和灵活性。同时,可以考虑使用字典来存储设备信息,以便更方便地进行查找和修改操作。另外,可以考虑使用函数来封装重复的操作,提高代码复用性和可维护性。最后,建议添加注释来说明代码的逻辑和用途,方便他人阅读和理解。
第六次大作业
设计与分析
家居强电电路模拟程序-2大作业相比于第一版增加了并联电路,并且要计算电路电阻给不同设备进行分压,因此要增加电路类,包括串联电路类与并联电路类,串联电路包括包含各种设备的集合,而并联电路类包含包含多条串联电路的数组,为方便输出处理,把整个电路看作一条串联电路,将其中的并联电路类也看作是一个以Device类为父类的子类型,串联电路类作相同处理
相关源码:
class Circuit {
List<Contact> contacts; //有多条串联电路
Parallel parallel; //一条并联电路
}
class Contact extends Device { //一条串联电路
List<Device> devices = new ArrayList<>();
public Contact(String name) {
this.name = name;
}
}
class Parallel extends Device { //并联电路
List<Contact> list = new ArrayList<>();
public Parallel() {
}
public Parallel(String name) {
this.name = name;
}
public List<Device> getDevices() {
List<Device> devices = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
Contact contact = list.get(i);
devices.addAll(contact.devices);
}
return devices;
}
public double getRate(Device device1){
double resist = 0;
Device device ;
for(int i=0;i<list.size();i++){
Contact contact = list.get(i);
for(int j=0;j<contact.devices.size();j++){
device = contact.devices.get(j);
if(device.useful&&device==device1){
for(int k=0;k<contact.devices.size();k++){
resist+=contact.devices.get(k).getResist();
}
}
}
}
return device1.getResist()/resist;
}
public double getResist() {
double[] resist = new double[list.size()];
//计算每一条并联电路的电阻
for (int i = 0; i < list.size(); i++) {
Contact contact = list.get(i);
for (int j = 0; j < contact.devices.size(); j++) {
Device device = contact.devices.get(j);
if (device instanceof Switcher && ((Switcher) device).flag == 0) {
resist[i] = -1;
for (int k = 0; k < contact.devices.size(); k++) {
contact.devices.get(k).useful = false;
}
break;
}
resist[i] += device.getResist();
}
}
for(int i=0;i<list.size();i++){
if(resist[i]==0)
return 0;
}
double ret = 0;
for (int i = 0; i < resist.length; i++) {
if (resist[i] == 0 || resist[i] == -1)
ret += 0;
else if (resist[i] != -1) {
ret += 1 / resist[i];
}
}
if (ret != 0)
return 1 / ret;
else
return 0;
}
}
主串联电路的设备与上一次的处理方式相同,重难点在于对并联电路的处理,由于这里将串联电路和并联电路都看作是Device类的子类,都为其给定Type值
public int getTypeValue() {
if (this instanceof Switcher)
return 1;
else if (this instanceof Continuer)
return 3;
else if (this instanceof Speeder)
return 2;
else if (this instanceof WhiteLight)
return 4;
else if (this instanceof SunLight)
return 5;
else if (this instanceof Fan)
return 6;
else if (this instanceof floorFan)
return 7;
else if (this instanceof Contact)
return 50;
else
return 100;
}
相关类图
踩坑分析
以下是几项注意事项:
1.这里的主串联电路上的其他设备与前面的处理方式大同小异,主要是并联上的所有用电器的处理,这里把并联电路整个看成是一个Device的子类,将并联中的所有电器全部封装到类中,但是最终输出时是按照主串联电路排序后的顺序依次输出的,因此需要把并联电路中的所有设备也写入一个集合中,并返回去形成一个完整的串联电路。
2.由于题目要求调速器只能连接在电源中,因此并联支路上不可能出现调速器,但是无论是在串联上还是在并联上都可能存在开关,串联上的开关影响整个电路,而并联的开关仅仅影响那一条支路而已,当并联所有支路都断开时整个电路才算断开
3.可以知道并联每条支路的电压是相等的,但并不意味着并联电路上每个设备的分压也相等,在一条支路上还要考虑多i个设备的串联分压,其次,并联电路的支路也并不一定有效,在确保该支路有效的前提下计算每条支路的分压然后再计算每个设备的分压
4.要注意并联电路和串联电路设备的分压计算公式
这里只考虑了并联电路中只有两条支路,且通过下标依次去除支路分别处理,考虑不完全
这里在计算了整个并联电路的分压后将该电压赋给了并联电路上的每个设备,没有考虑在一条支路上可以有多个设备继续分压,其次,这里未考虑某些特殊情况比如并联电路电路但是未产生无穷大电流烧毁电路,还有,如果并联电路上的所有支路全部断路,则整个电路断开。
由于此次题目没有测试点提示,需要自己创建测试点对代码进行测试,要注意边界测试重点对一些可能出现的特殊情况多考虑
改进建议
对并联的处理仍不恰当,如果有多个并联电路或存在串联电路中包含了其他串联电路,则并联电路中处理并联分压的方法不再使用,其次,在读取信息时应该依次读取,并且遍历所有设备,如果不存在则创建,如果已经存在,就不再创建。如果使用间隔读取的方式可能会遗漏某些电器。由于病来你电路可能不存在一条,因此在整个的电路类中应该有并联类的数组,以便处理多条并联电路
三次作业的大总结
1.这三次练习让我学到了面向对象编程的封装,继承,多态的好处,对代码的可复用性和延展性的理解有了进一步的提升,也学会了更好地设计类与类之间的关系,但是在代码的优化上仍有不足
2.画出来的类图太单调,类与类之间的关系和练习很模糊,要尽量把类之间能建立起一定的关联,这样有利于简化代码。究其原因是对业务逻辑的分析没有到位,并且缺少事后的代码的分析和优化
3.第六次大作业的测试点没有提示,需要自己想测试点一个个尝试,很麻烦,希望给点提示,如过没有提示的话希望能多点测试样例,或者测试样例能有代表性,有什么特殊情况的话能给点信息,哪些情况要考虑哪些情况不要考虑
4.一系列题目都是迭代,前面的写不来后面动不了笔,一个星期不到就要写一次,临近期末时间较紧,还需要点时间把前面写不出的再想想