首页 > 其他分享 >oop-PTA题目集4~6总结

oop-PTA题目集4~6总结

时间:2024-06-06 15:33:46浏览次数:23  
标签:输出 题目 PTA 电路 oop 方法 输入 设备

一、前言

     相比于前三次的题目集,题目集4~6需要用到的新的知识点主要是面向对象程序设计中继承和多态这两个特性的使用。第四次题目集中的答题判题程序-4是在前三次程序的基础上增加新的内容,新增了选择题和填空题两种题型,这一变化的处理需要用到前三次未使用的继承,即将题目类作为父类,选择题和填空题作为该类的两个子类。与此同时,信息的输入也有明显变化,即可以以任意的先后顺序输入各类不同的信息,对于输入的处理要求更高。另外,排序也是本次题目新增的一大内容,可以使用接口来处理这一问题。异常情况的处理与前三次基本相同。本次题目考察的主要就是正确地使用继承来处理变化,难度总体上不大,但是对输入内容和异常情况的处理较复杂,容易出现问题,尤其是“多学生 多试卷 乱序输入”这类复杂输入的处理较麻烦。该题题目内容较多,容易忽视一些重要信息,完成时需仔细读题,认真分析题目。而从第五次题目集开始是新的一轮迭代,即家居电路模拟程序,相较于答题判题程序,本次的考察重点是类和类间关系的设计,程序中继承的和多态的合理运用是解决问题的关键。本次题目涉及多种电器设备,在之后的题目中也会增加新的设备,这时就需要关注面向对象程序设计原则中的开闭原则,合理进行抽象化是尤为重要的,即需要提取题目中各种具体对象的共同特征并建立对应的抽象类,定义抽象方法,再创建各种具体类继承该抽象类,在具体类中实现抽象方法,这一思路是解决本系列题目的一大重点,此外,通过父类(抽象类)引用指向子类对象这一形式以及通过父类引用调用在子类中实现的抽象方法是解决本题的另一大重点。第一次迭代难度较小,只考察串联电路,且电路中只有一个受控设备,且有许多限制条件降低了难度,所以计算部分较为简单,主要是输入信息的解析和最终的排序输出数据需要注意,另外,由于会产生小数,本题在输出时对于数据精度的处理也要特别注意。本题虽简单,题目内容却较多,蕴含的信息较多,需要仔细读题,认真分析题目内容。第六次的题目集中的题目的变化主要是电路结构、计算方式和电路元件的变化,设计时需要考虑的内容增多,电压的计算和多种断路情况是本题的难点,与并联电路相关的类的设计十分关键,由于输入的信息种类增多,对输入的处理需要改进,但是有一些输入的信息在本题中无太大的影响和作用,对输入进行解析时可以适当取舍,处理对本题而言的有效信息而忽略对本题结果不会造成影响的信息,这是简化做题的方法。同样,由于运算更加复杂,对计算的处理和数据精度的处理需要更加细致。本题另一核心就是开关处理,需要综合考虑多种情况,但本题在输入上均是正常输入,测试点也无短路等异常测试,总体难度比第一次略大,但认真分析较长的题干,找准关键信息,针对新变化设计新的类和方法,本题还是比较容易解决的。总体上,这三次题目集中都涉及面向对象程序设计继承性和多态性的运用,同时也涉及按一定条件对对象进行排序这一内容。下面将对这三次的题目进行具体分析。

二、设计与分析

    第四次题目集的答题判题程序-4相较于前三次新增的内容如下:

1、输入选择题题目信息

题目信息为独行输入,一行为一道题,多道题可分多行输入。

格式:"#Z:"+题目编号+" "+"#Q:"+题目内容+" "#A:"+标准答案,格式基本的约束与一般的题目输入信息一致。

新增约束:标准答案中如果包含多个正确答案(多选题),正确答案之间用英文空格分隔。

多选题给分方式:答案包含所有正确答案且不含错误答案给满分;包含一个错误答案或完全没有答案给0分;包含部分正确答案且不含错误答案给一半分,如果一半分值为小数,按截尾规则只保留整数部分。

2、输入填空题题目信息

题目信息为独行输入,一行为一道题,多道题可分多行输入。

格式:"#K:"+题目编号+" "+"#Q:"+题目内容+" "#A:"+标准答案,格式基本的约束与一般的题目输入信息一致。

填空题输出:

输出格式与一般答卷题目的输出一致,判断结果除了true、false,增加一项”partially correct”表示部分正确。

填空题给分方式:

答案与标准答案内容完全匹配给满分,包含一个错误字符或完全没有答案给0分,包含部分正确答案且不含错误字符给一半分,如果一半分值为小数,按截尾规则只保留整数部分。

3、输出顺序变化

只要是正确格式的信息,可以以任意的先后顺序输入各类不同的信息。比如试卷可以出现在题目之前,删除题目的信息可以出现在题目之前等。

4、多张试卷信息

本题考虑多个同学有多张不同试卷的答卷的情况。输出顺序优先级为学号、试卷号,按从小到大的顺序先按学号排序,再按试卷号。

针对此变化,设计了选择题类和填空题类,与题目类是继承关系,三个类所含的属性和方法以及类间关系如下类图所示:

其中,作为父类的题目类提供了一个用于具体的特殊题目的判题方法,即isCorrect方法,这是专门给子类用于实现不同的判题给分方式的方法,在两个子类中重写该方法,即可以使选择题和填空题对象调用方法名相同的判题方法时产生与之对应的判题给分结果。父类中仍保留了一般题目的判题方法,即isRightAnswer方法用于和前三次程序中的普通题目的判题。另外,两个子类的构造方法中都使用了super语句调用父类的有参构造方法,因为选择题和填空题和一般题目所具有的属性相同,即题号、内容、标准答案和分值。而父类中的剩余方法都是各属性的get和set方法,不必多说。

其中,填空题对应的判题方法的代码如下所示:

 1   public int isCorrect(String ans){
 2         if(ans==null){
 3             return 0;
 4         }
 5         if(ans.equals(standardAnswer)){
 6             return 1;
 7         }
 8         if(standardAnswer.contains(ans)&&!ans.isEmpty()){
 9             return 2;
10         }
11         return 0;
12     }

由于填空题的给分有三种情况,所以在该方法中使用了三个if语句判断答案符合哪一种情况,返回不同的整数值。第一种情况是答案为空,返回值为0;第二种情况是答案内容与标准答案完全相同,返回值为1;第三种情况是答案内容为标准答案的一部分,返回值为2。

选择题对应的判题方法的代码如下所示:

 1  public int isCorrect(String ans){
 2         int j;
 3         if(ans==null){
 4             return 0;
 5         } 
 6         String[] answerChoice=standardAnswer.split(" ");
 7         String[] a=ans.split(" ");
 8        if(ans.equals(standardAnswer)){
 9             return 1;
10         }
11         if(a.length==answerChoice.length){
12             for(int k=0;k<a.length;k++){
13                 for(j=0;j<a.length;j++){
14                     if(a[k].equals(answerChoice[j])){
15                         break;
16                     }
17                 }
18                 if(j==a.length){
19                     return 0;
20                 }
21             }
22             return 1;
23         }
24         for(int k=0;k<answerChoice.length;k++){
25         if(ans.contains(answerChoice[k])){
26             for(int i=0;i<a.length;i++){
27                 if(!standardAnswer.contains(a[i])){
28                     return 0;
29                 }
30             }
31             return 2;
32         }
33         }
34         return 0;
35     }

选择题的判题相较于填空题来说要考虑的因素更多,首先是答案为空的情况判为零分,返回值为0,之后由于各答案选项间由空格分开,于是使用了split方法将答案和正确答案的选项拆分,如果答案和标准答案完全相同,返回值为1。之后就比较麻烦了,尤其是答案中同时有正确和错误的选项时的判断,使用了循环的嵌套和多个if语句判断,首先是答案与标准答案数量相同的情况,可能出现部分选项答案错误的情况;然后是标准答案包含答案的情况。我采用的是通过循环将答案与标准答案逐一比对判断,这导致了方法的圈复杂度高,可读性较差,在之后的程序中在完成各个方法的方法体的内容编写前要仔细思考最优的实现方法,即尽量少使用分支结构来处理问题,提高代码的可读性,使程序更容易理解,同时遇到问题时修改更为容易。

由于本题涉及多张答卷时的按多种条件排序输出,在答卷类中实现了Comparable接口,并实现CompareTo方法,即制定排序的标准。由于存储答卷对象的数组是一般的定长数组,在实现该方法后通过调用Arrays类中的sort方法并将该数组作为参数可实现对答卷对象的排序,其中CompareTo方法的实现代码如下所示:

1   public int compareTo(AnswerSheet o){
2              if(!this.studentNumber.equals(o.studentNumber)){
3             return this.studentNumber.compareTo(o.studentNumber);
4                 }
5             else{
6                 return this.numOfSheet-o.numOfSheet;
7             }
8         }

在该方法中,将待比较的答卷对象作为参数,由于按照优先级不同的两个条件进行排序,故先用if语句判断优先级高的学号是否相同,如果不同,由于学号是String类,而String类中实现了CompareTo方法完成字符串比较,故在return语句中返回该答卷对象的学号调用的CompareTo方法与参数对象比较后返回的整数值,即完成学号从小到大排序。若学号相同,则返回该答卷对象试卷号与参数对象试卷号的差值,完成按试卷号从小到大排序。

 以下是由SourceMonitor生成的部分分析结果:

 通过以上结果可以发现,在本次程序的代码中注释量过低和最大复杂度过高是两大问题,。其中,主方法的圈复杂度最高,为14,其次,多选题判题方法的圈复杂度也较高,使用了大量的if判断语句和for循环,分支语句和嵌套次数较多,这会导致程序结构复杂,出现问题时难以发现和解决,程序的可读性也较差,主方法部分中可以考虑将所有不必要的多余职责作为方法写入其他的类中,比如可以将一大段解析输入字符串的操作放在与输入的解析相关的类中,这能有效地降低圈复杂度。此外,在写程序时养成写注释的好习惯是很有必要的,出现问题时也便于更快找到问题所在,且一段时间后再阅读程序时更容易理清逻辑,而不至于看不懂之前写的程序。

第五次题目集中的家居电路模拟程序是一道全新的题目,其中题目中的主要信息如下:

1、控制设备模拟

开关:包括0和1两种状态。开关有两个引脚,任意一个引脚都可以是输入引脚,而另一个则是输出引脚。开关状态为0时,无论输入电位是多少,输出引脚电位为0。当开关状态为1时,输出引脚电位等于输入电位。

分档调速器:按档位调整,常见的有3档、4档、5档调速器,档位值从0档-2(3/4)档变化。本次迭代模拟4档调速器,每个档位的输出电位分别为0、0.3、0.6、0.9倍的输入电压。

连续调速器:没有固定档位,按位置比例得到档位参数,数值范围在[0.00-1.00]之间,含两位小数。输出电位为档位参数乘以输入电压。

2、受控设备模拟

灯有两种工作状态:亮、灭。在亮的状态下,有的灯会因引脚电位差的不同亮度会有区别。

风扇在接电后有两种工作状态:停止、转动。风扇的转速会因引脚的电位差的不同而有区别。

白炽灯:亮度在0~200lux(流明)之间。 电位差为0-9V时亮度为0,其他电位差按比例,电位差10V对应50ux,220V对应200lux,其他电位差与对应亮度值成正比。白炽灯超过220V。

日光灯:亮度为180lux。 只有两种状态,电位差为0时,亮度为0,电位差不为0,亮度为180。

吊扇:工作电压区间为80V-150V,对应转速区间为80-360转/分钟。80V对应转速为80转/分钟,150V对应转速为360转/分钟,超过150V转速为360转/分钟(本次迭代暂不考虑电压超标的异常情况)。其他电压值与转速成正比,输入输出电位差小于80V时转速为0。

 由题目可知,本题中的设备分为控制设备和受控设备两类,其中,控制设备包括开关和调速器两大类,调速器又分为分档和连续两类;受控设备包括灯和风扇两类,灯包括白炽灯和日光灯两类,本题中的风扇只包含一种吊扇。根据对设备的分类,设计的部分相关类对应的类图如下所示:(具体的受控设备类和接口省略)

我设计了Equipment类作为总设备类,即所有电器设备共同的抽象父类,内含所有设备都具有的设备编号这一属性,由于最后的输出需要排序,故实现了Comparable接口,实现其中的CompareTo方法制定排序规则,另外还有一个供具体电器设备工作时输出对应工作数据的抽象方法,即printData方法。此外,设计了总受控设备的抽象父类,具有受控设备共有的电压和状态这两个属性,风扇和灯作为抽象的受控设备继承了总受控设备类,各自含有具体的受控设备的工作时对应数据的属性,如灯的亮度和风扇的转速,同时各含一个抽象的计算方法供具体的灯或风扇实现。对于控制设备,暂时没设计抽象的总控制设备类,而时开关和调速器继承总设备类,其中调速器为抽象类,以所有调速器共有的输入电压作为属性,有一个获取输出电压的抽象方法,供具体的调速器实现,所有的具体设备类都实现了输出工作数据的方法及其所有抽象父类的其他抽象方法。总体上的设计层次较为清晰,提取了一些具体对象共有的属性和行为构建抽象层,有利于简化新变化产生时的处理,使类的设计尽可能符合开闭原则。

另外,由于本次大作业中的引脚都是规范输入,即两个引脚一进一出,不存在异常情况,故未设置与之对应的属性,即可以考虑对输入信息的解析进行简化,那些对题目来说无决定性影响的信息可以考虑不进行处理。

在本次程序中,我设计了一个元素集合类,对应代码如下:

1 class ElementSet{
2     private ArrayList<Equipment> equipmentList=new ArrayList<>();
3     public ArrayList<Equipment> getEquipmentList(){
4         return equipmentList;
5     }
6 }

该类专门用于储存所有的电器设备,内含一个名为设备列表的ArrayList动态数组的属性,其中数组中存储元素类型为Equipment类,即总设备类,因所有具体设备作为子类都是抽象父类的实例。在主方法中创建该类对象,用于将输入解析后创建的设备对象储存在该类中的数组中,该类职责明确,这样处理便于最后的输出数据。

本次程序中设计了一个专门用于输出结果的输出类,对应代码如下:

1 class Output{
2     public static void outputData(ElementSet e){
3         Collections.sort(e.getEquipmentList());
4            for(int i=0;i<e.getEquipmentList().size();i++){
5                 e.getEquipmentList().get(i).printData();
6     }
7     }
8 }

该类中含有一个输出方法,该方法先对传入的集合对象中的设备列表进行排序,后遍历该列表,调用各具体设备的输出数据方法。这里的处理实现了多态,即获取的具体设备都是抽象父类Equipment类型对象,即完成了向上转型,并且具体设备类都实现了printData()这一抽象方法,程序运行时根据不同的具体设备对象的类型完成这一方法的不同实现,这样输出时就不必判断对象的具体类型再使用与之对应的方法输出,即可减少if语句的使用,降低圈复杂度,使代码更简洁易读。

 以下是由SourceMonitor生成的部分分析结果:

通过以上的分析结果不难发现,本次题目的代码复杂度相较于上一次题目有明显的降低,尤其是主方法的圈复杂度降低尤为明显,这与更加合理的类的设计密切相关。本次对类职责的划分有了更认真的考虑。主方法中不执行具体的职责,将职责尽可能细分,作为不同类中的方法,如创建了输入解析类、输出类、处理类等,在主方法中只需创建对象和调用对应类中的方法即可,这使主方法的代码量有明显降低。但还存在部分类中方法的复杂度相对较高的问题,如dealWithPD()方法的圈复杂度为9,设计时考虑不周,在之后的设计中要更加注意,尽可能在单个方法中减少分支语句的使用。此外,本次题目仍未写注释,需要在之后写程序时注意在必要出给出注释,养成写注释的好习惯,使程序更易于理解。

第六次题目集中的题目是在第五次的题目的基础上增加新内容,新增的主要内容如下:

所有开关的电阻为 0。本次迭代模拟一种落地扇,工作电压区间为 [80V,150V],对应转速区间为 80-360 转/分钟。电压在[80,100)V 区间对应转速为 80 转/分 钟,[100-120)V 区间对应转速为 160 转/分钟,[120-140)V 区间对应转速为 260 转/分钟,超过 140V 转速 为 360 转/分钟(本次迭代暂不考虑电压超标的异常情况)。本次迭代考虑电阻:白炽灯的电阻为 10,日光灯的电阻为 5,吊扇的电阻为 20,落地扇的电阻为 20。

输入串联电路信息:一条串联电路占一行,串联电路由按从靠电源端到接地端顺序依次输入的 n 个连接 信息组成,连接信息之间用英文空格" "分隔。输入并联电路信息:一条并联电路占一行,并联电路由其包含的几条串联电路组成,串联电路标识之间用英文空格" "分隔。

由于本次迭代涉及并联电路,电路结构更加复杂,故针对电路新设计了三个类,展示各类的属性和方法及类间关系的类图如下所示:

我设计了一个抽象的电路父类,包含各种电路都具有的在该电路上的设备列表和该电路的电流这两个属性,提供了一个计算该电路上总电阻的抽象方法供具体电路实现。而具体的电路我设置了并联分支电路和总串联电路两个具体类,其中并联分支电路是组成一个并联电路的多条串联电路,即并联中的单条串联线路,实现了计算总电阻的方法,同时有一个通过电路的电流计算并设置该电路上每个设备对应电压的方法,还有一个判断该条串联支线是否是闭合电路,即是否有开关断开情况,这种情况需另作处理。而另一个总的串联电路类则将并联电路整体当作一种设备,相当于将电路转化成了一条串联主电路,该类实现了计算主电路总电阻的方法,同时也有一个设置包括并联电路在内的各设备的电压的方法。总体上的设计比较清晰明了,不过,还有可以优化的地方,比如设计一个并联电路类,这样也有利于后续多个并联电路的处理。

本次的题目设计电阻、电流、电压的相关计算,其中主电路电阻的计算方法代码如下所示:

 1  public void calculateR(){
 2         double sumR=0,lineR=0;
 3         for(Equipment e:this.getEqList()){
 4             if(e instanceof BranchLine&&((BranchLine) e).isClosedLine()){
 5                 lineR+=1/e.getR();
 6             }
 7             if(!(e instanceof BranchLine)){
 8                 sumR+=e.getR();
 9             }
10         }
11         if(lineR!=0){
12             lineR=1/lineR;
13         }
14         sumR+=lineR;
15         this.setR(sumR);
16     }

 主电路的设备可分为两类,一类是具体的设备,一类是并联电路中的分支电路。该方法通过循环遍历所有设备,通过if语句判断设备是哪一类,如果是分支闭合电路,则按照并联电路总电阻的计算方式进行处理,如果不是分支闭合电路,按照串联电路总电阻计算方式处理,之后算出并联部分总电阻再按串联电路总电阻计算方式计算,得到主电路总电阻,完成计算。

此外,为更好地处理多种设备的排序问题,我还在设备类中新增了一个不同类具体设备的标号属性,新增的属性如下所示:

1  private int IDnum;

每种具体设备类都有属于该类的一个固定的标号,根据标号大小可以确定不同类设备间的顺序,这样更易于制定排序规则。在创建每种具体设备的对象时,都会设定其对应的标号,即在构造方法的方法体内部包含这一属性的set方法来实现。这能使compareTo方法的方法体的内容得到简化。

以下是SourceMonitor生成的部分分析结果:

 

 通过以上的分析结果可以看出Max Complexity这一指数偏高,同时% comments这一指数过低,出现这种情况的原因与类中方法的设计不合理有一定关系,可以考虑将部分类中的方法进行调整,首先要确保各方法的职责的单一性,然后尽可能避免出现过多的if语句、for循环语句以及嵌套语句,另外,尽可能保证每个方法的方法体部分代码不超过20行,对一些复杂度高的类中的方法,如switchAdjust方法,可以考虑对该方法进行分析,看是否能再拆分出几个方法,尽量使各方法内容简洁清晰。此外,注释的问题就不必多说了,这主要是要在写程序的过程中要对那些不易于理解的部分加以关注,尽量给出必要的注释。

 三、踩坑心得

在第四次的题目中,答卷的最终排序出现过一个使用定长数组容易产生的问题,完成排序的方法调用对应的代码为:

1 Arrays.sort(answerSheet);

这是比较容易出现的问题,因定长的对象数组的长度是确定的,使用new操作符初始化后所有的下标对应的引用值默认都是null,而创建对象后将对象赋值给数组下标对应的元素后,该引用对该题来说才是有效的,而答卷对象的总数小于数组的长度,这会导致一些下标对应的数组元素对该题来说是无效引用,排序时不该将它们考虑在内,即只考虑存储了答卷对象的那部分下标对应的数组元素,而上面的代码是对整个数组排序,即包括了多余的无效的数组元素,这是使用定长数组的一个弊端,使用集合类中的ArrayList动态数组就不会出现类似问题,修改后的代码如下:

1 Arrays.sort(answerSheet,0,s);

由于存储答卷对象是按下标从小到大顺序存储的,故可以是用Arrays类中sort的重载方法,即指定排序的下标范围。以后如果遇到定长数组按一定条件排序时一定要多加注意,要确定排序的区域,而不是对整个数组排序,同时,以后的程序尽量避免使用定长数组,学会使用集合类中的容器可以省去一些不必要的麻烦。

 在第五次的题目中,出现了最终输出的数据与预期结果有微小差距的情况,如预期结果是:@L1:0.68 @B2:149,而最终输出结果却是:@L1:0.68 @L2:150。出现这种情况的原因是白炽灯的相关计算中会产生小数,而最后输出时处理不恰当产生误差,出现问题的相关代码如下:

1 System.out.println("@B"+this.getNum()+":"+String.format("%.0f",this.calculateBrightness()));

在进行数据的输出时,使用的是格式化输出,这种输出是四舍五入输出,而不是严格的向下取整输出,将最后的亮度的输出修改后便能正常运行,即使用将数据向下取整的方法,修改后对应代码如下:

1 System.out.println("@B"+this.getNum()+":"+(int)Math.floor(this.calculateBrightness()));

这里使用了Math类的向下取整的floor方法,由于该方法返回值为double类型,还需要进行强制类型转换。在之后的题目中,一定要认真阅读题干,理清题目要求,关注数值的输出,尤其是输出结果的精度要求,并选择恰当的方法对数据做相关处理。

 此外,在第五次的题目中,对于分档调速器的处理也出现了一个我找了一段时间后才发现的错误,这个错误还是较为明显的,在本次题目中的分档调速器为4档调速器,但对档位的处理并没有判断是否超出档位范围,导致调节后输出时的数据可能产生错误,下面以调高档位的方法为例,如下是修改前的方法的代码:

1  public void gearUp(){
2         gear++;
3     }

在这个方法中,如果档位超过范围后调用此方法,档位仍会增加,但事实上档位应是不变的,这就产生问题,只需要在方法中添加一个if语句即可解决,即档位小于3时,档位加1。这提醒我审题和分析问题的重要性,在写程序时,一定要考虑多种可能的特殊情况,尽可能全面地考虑问题。

在第六次的题目中,解析主电路信息的标志选择错误导致有少数的测试点没通过,一开始我是以是否包含"M"为标志判断是否是主电路,后来改为是否包含"VCC"为标志判断。这提醒我思考问题要全面,要从多方面考虑可能出现的情况,当出现问题时也要对特殊的情况加以考虑,我在部分断路情况的判断也出现了考虑不全面的问题,主要是最后处理断路情况的方法存在缺陷,这使我思考了很久才得到解决。对此,我建议在分析题目后要先把可能作为测试点的所有情况列出来,再根据这些情况调整代码,可能有利于减少某些测试点一直无法通过或者通过了这些测试点却导致其他测试点无法通过的情况发生。

四、改进建议

  第四次的答题判题程序中,主方法部分存在方法体内容过多的问题,且执行了多个具体的职责,这违背了单一职责原则,其中的输入解析部分中有较多if语句,相关代码如下:

 1          while(!s1.equals("end")){
 2               if(!Validate.judgeFormat(s1)){
 3                   System.out.printf("wrong format:%s\n",s1);
 4               }
 5               else{
 6               if(s1.contains("#N:")){
 7                   type=0;
 8                       ParseInput.parseQuestion(s1,qu,type);
 9                       count1++;
10               }
11               if(s1.contains("#T:")){
12                   paperstr[pnum++]=s1;
13               }
14               if(s1.contains("#S:")){
15                   answerstr[s++]=s1;
16               }
17               if(s1.contains("#X:")){
18                   String[] s3=s1.split("(#X:| |-)");
19                   for(int k=1;k<s3.length;k+=2){
20                   answerStudent[num3]=new AnswerStudent(s3[k].trim(),s3[k+1].trim());
21                       stuMap.put(s3[k].trim(),answerStudent[num3]);
22                       num3++;
23                   }
24               }
25               if(s1.contains("#D:")){
26                   deletestr[q++]=s1;
27               }
28                  if(s1.contains("#Z:")){
29                      type=1;
30                      ParseInput.parseQuestion(s1,qu,type);
31                       count1++;
32                  }
33                   if(s1.contains("#K:")){
34                       type=2;
35                        ParseInput.parseQuestion(s1,qu,type);
36                       count1++;
37                   }
38               }
39               s1=input.nextLine();
40           }

可以看到,光对输出进行处理就占了40行,使用了8个if语句,虽然已经创建了输入解析类,类内也有一些解析特定对象的方法,但还有优化的空间,比如可以将while循环中的整个解析过程作为方法放入输入解析类中,同时答题学生的解析也像其他对象解析一样在解析类中设置一个解析的方法。不过,由于涉及的变量过多,会出现方法参数数量过多以及该方法的复杂度依旧较高的问题,可以将本体中的所有定长的普通数组修改为集合类中的ArrayList,这样可以减少变量的使用,大幅度简化程序,同时,可以考虑将本次程序中的需特别关注的解析对象(如与引用异常有关的对象)和其他的对象的解析写入两个不同的方法作为解析类中的方法,即寻找不同信息的共同点和不同点,对职责进行再度细分,作相关改进,可以很大程度上解决主方法内部结构复杂的问题,提高程序可读性,同理,在解析完字符串后创建特殊的对象(引用相关)的部分同样可作为方法写入对应的类中,以简化主方法。

本题中另外一个可以改进的地方是与题目相关的类的设计,由于出现多种类型的题目,可以考虑将题目类设计成抽象类,将判题方法isCorrect()设计成抽象方法,同时删去普通题目的判题方法,另设一个普通题目类继承题目类,并实现判题方法,这样的设计更加合理,更有利于应对变化,更符合开闭原则,新增加的普通题目类的代码大致如下:

 1 class NormalQuestion extends Question{
 2       public NormalQuestion(int number,String content,String standardAnswer){
 3         super(number,content,standardAnswer);
 4     }
 5     @Override
 6     public int isCorrect(String ans){
 7         if(standardAnswer.equals(ans)){
 8                 return 1;
 9             }
10             return 0;
11         }
12 }

在第四次的题目中还出现了部分可以简化的重复代码,如以下代码:

 1  if(ques instanceof MutipleChoiceQuestion&&!judgeOutcome[m]){
 2                      if(ques.isCorrect(answerList[m])==2){
 3                      System.out.println(q+"~"+answerList[m]+"~"+"partially correct");
 4                      return;
 5                      }
 6                  }
 7                  if(ques instanceof BlankFillQuestion&&!judgeOutcome[m]){
 8                       if(ques.isCorrect(answerList[m])==2){
 9                      System.out.println(q+"~"+answerList[m]+"~"+"partially correct");
10                      return;
11                  }

可以看到,两个外层if语句后的内容完全相同,可以考虑将这两个if语句合并为一个,即将这些判断条件写在一个if语句后的括号中,可以减少分支语句的数量,降低圈复杂度。本次程序的其他部分的分支语句中重复代码也可进行类似的处理。在之后的程序中要多加注意,写程序时要多加思考,尽量不要出现重复代码,有一些代码中有公共的部分时也可以考虑将这一部分的内容写在方法中,并通过调用该方法实现一段代码的重复使用。

在第五次题目的电路模拟程序中,可以考虑设计一个控制设备类作为抽象父类继承设备类,并让开关和调速器类继承控制设备类。另外可以尝试在受控设备类中添加一个boolean类型的用于判断设备是否处于工作状态的属性,即判断电路中出现断路情况,这样有利于最终电压的处理。

在第六次题目中,由于BranchLine类和BusLine类都有一个设置设备电压的方法,故可以在类方法的设计上稍作修改,在抽象电路类中添加一个设置电压的抽象方法,再在这两个类中实现这个方法,这样处理有利于实现多态,使程序结构更清晰,即在电路类中添加这一抽象方法:

1 abstract void setEquipmentPD(double totalPD);

 五、总结

    通过这几次的题目,尤其是第五次题目和第六次题目中抽象类的使用,我逐渐理解了面向抽象编程的方式。面向抽象编程,是指当设计某种重要的类时,不让该类面向具体的类,而是抽象的类,即所设计的类中的重要数据是抽象类声明的对象,而不是具体类声明的对象,面向抽象的程序设计将设计层次提高到系统整体设计的层次,通过这样的设计可以设计出高扩展、低耦合的系统。正确并灵活使用抽象类是十分必要的,在第五次题目中就采用了整体分析的设计方式,即找出所有具体的对象的共同特征和行为,抽象出这些具体对象的共同父类,在抽象类中定义不同子类有不同的实现的抽象方法,如第五次的题目的设备类、受控设备类,第六次题目中的电路类都是抽象类,此外,我还逐渐学会了对象的向上转型和多态的应用。如在第六次的题目中,在对连接信息进行解析和创建具体设备对象的方法中就很好地利用了子类对象是父类的实例这一特性。在这两个方法中都将Line类型的对象作为其中的一个参数,并调用了两个具体电路子类都具有的方法,这样根据传入的对象子类型的不同可调用各自对象的对应方法而不必进行多余的判断或其他处理,可以很大程度上简化代码,并提高代码的复用性,使代码更灵活。在设计时,我的考虑不是十分全面,比如存在有些具体子类的共有的一些属性或行为未在抽象类中定义的情况,也存在多次使用对象强制类型转换的情况,这说明,对于多态的使用还不是很熟练,更确切地说,在设计时就没有认真考虑导致难以实现多态,这也提醒我在之后的设计中要多从整体上分析和设计,即学会模块化的设计,并在合适之处使用多态。这使我明白先设计再实现的重要性,即哪到题目要先进行细致地分析,而不是直接开始写代码,培养分析问题和针对问题进行设计的能力使十分重要的。不过,在第五和第六次题目中,我学会了如何使主方法更精简,避免了主方法中过多变量、过多分支语句的问题,还对java中的集合类中的ArrayList的使用更加熟练,在之后的程序设计中也可尝试使用集合类中的其他容器。在第五和第六次题目中,我尽可能地使各方法中的代码量不至于太多,进行了一些考虑,在之后的程序设计中,也要在方法体的内容上加以考虑,尽量让所有的方法中的代码行数不超过20行,使代码结构和层次清晰,使方法的复用性增强。最后,耐性和细心也是解决问题必不可少的因素,要学会静下心来分析问题。至于相关意见,我没什么要提的,不过,我希望教师给我们提供一些学习面向对象程序设计的有效的方法。在之后的学习中,我会对存在的问题进一步改进,对薄弱的环节进行进一步的学习和研究。

标签:输出,题目,PTA,电路,oop,方法,输入,设备
From: https://www.cnblogs.com/23201218-lxl/p/18227611

相关文章

  • 2种方法解决需要clik点击数的题目——[HNCTF 2022 WEEK2]getflag 137分 MFC patch RE
    题目   DIEIDA找到判断点击数的if,我们修改一下汇编指令让点击数<99999999就成立这个程序没有要求我们输入,说明flag是程序打印的IDA动调 下一个断点修改 得到flag  还有一种更快的方法——CheatEngine 随便点击几次 在CE中修改点击次数 Getf......
  • Android课程设计课题题目推荐(安卓期末大作业,毕业设计,Androidstudio)
    博主介绍:本人专注于Android/java/数据库/微信小程序技术领域的开发,以及有好几年的计算机毕业设计方面的实战开发经验和技术积累;尤其是在安卓(Android)的app的开发和微信小程序的开发,很是熟悉和了解;本人也是多年的Android开发人员;希望我发布的此篇文件可以帮助到您;......
  • C++课程设计杭电题目(中)
    2073.无限的路题目描述http://acm.hdu.edu.cn/showproblem.php?pid=2073http://acm.hdu.edu.cn/showproblem.php?pid=2073ProblemDescription甜甜从小就喜欢画图画,最近他买了一支智能画笔,由于刚刚接触,所以甜甜只会用它来画直线,于是他就在平面直角坐标系中画出如下的图形:......
  • 新手上路:Linux虚拟机创建与Hadoop集群配置指南①(未完)
    一、基础阶段Linux操作系统:创建虚拟机1.创建虚拟机打开VM,点击文件,新建虚拟机,点击自定义,下一步下一步这里可以选择安装程序光盘映像文件,我选择稍后安装选择linux系统位置不选C盘,创建一个新的文件夹VM来放置虚拟机,将虚拟机名字改为master方便后续识别(也可以改为其他......
  • WPF界面反编译神器Snoop使用介绍
    Snoop介绍Snoop是一款开源的WPF监视工具,它能够监视或浏览任何正在运行的WPF应用程序的可视化、逻辑和自动化树(无需调试器),还可以更改属性值、查看触发器、在属性更改时设置断点等。项目地址:https://github.com/snoopwpf/snoopwpf 运行可以到githubrelease下载已经编译......
  • docker部署hadoop集群
    docker部署hadoop集群:https://blog.51cto.com/865516915/2150651 一、主机规划3台主机:1个master、2个slaver/workerip地址使用docker默认的分配地址:master:主机名:hadoop2、ip地址:172.17.0.2 slaver1:主机名:hadoop3、ip地址:172.17.0.3主机名:hadoop4、ip地址:172.17......
  • 05 Hadoop简单使用
    目录一、hadoop安装配置二、运行hadoop三、hadoop2.x和hadoop3.x变化四、HDFS常用命令五、Java操作HDFS六、MapReduce七、压缩八、yarn常用命令一、hadoop安装配置​1、下载解压hadoop-x.x.x.tar.gztar-xzvfhadoop-x.x.x.tar.gz​2、下载解压jdktar-xzvfj......
  • blog2 4--6周PTA训练总结
    一.前言经过了第二轮4-6周的面向对象程序设计的学习,我对于这门课程的理解又更进一步。如同老师所说的,这门课程学下来,你会发现真正困难的从来不是敲代码的部分,而应当时设计的部分。在看到一道题之后第一时间想到不再是直接写代码,而是认真的反复查阅,构思一幅逻辑完整且正确的类图,它......
  • nchu-oop训练集4~6总结
    一、前言经过了前三次pta作业的练习,对pta作业也有了一定的了解,大作业4是与前三次作业相关联的,5、6次作业则是对家居强电电路的模拟,对于我来说还是存在着不小的难度,在进行家居强电电路模拟的过程中,我遇到了一些困难。比如,在布置电路结构时,需要考虑每个电器的功率和电压需求,以及安......
  • Trie字典树和AC自动机 (题目&答案)
    A.三年二班的投票题目描述三年级二班已经完成了竞选班长的投票,已知一共有n张投票,每张投票上写了一位同学的名字。投票统计结束后,张老师随意问一个同学的名字,请编程快速检索出,该同学共有几票。输入第一行读入一个整数n,代表产生了n张投票。(n≤)接下来n行,每行有一......