PTA第二阶段三次作业总结
前言
第二阶段的三次大作业明显对设计的要求进一步提高了。我三次作业一共得到了196分,答题判断程序-4有两个测试点未通过,其他两次作业均通过所有测试点,但程序仍有许多不足,下面开始逐题分析。
7-1 答题判题程序-4
答题判断程序-4增加的多选题,填空题,本质是考察了字符串的比较,运用contains,equals函数可以解决这两类题目的答案比较的问题。这两种题目类型的类,他们都继承自同一个父类Question类。在这次作业中,相比之前一次作业,我添加了一些父类,如题目的父类Question类,题目判断的父类Judge类,使用这些继承关系让我对代码的结构有了更好的设计。
整体思路如下:
首先调用JudgeInput类,判断输入是否有误,有误则输出输入有误,并将这条有误的信息保存在信息类Information里,无误则之间保存在信息类Information里。然后对Information类里的信息进行遍历,对不同类型的信息进行相应处理,比如题目信息,就new出Question类进行保存,试卷信息,就从已经创造的Questions类中遍历寻找需要的Question类,找到就放入对应信息,没找到则保存未找到该题信息。再处理答卷,调用Question内部的判断函数,让Question类自行判断答案是否正确,获得相应分数。最后进行答卷信息的输出。
下面简要分析下Question类:
属性:
1.属性type表示类型,该值对应为0,1,2分别表示简单题,多选题,填空题,-1表示原始类型。
2.属性order应该为int类型,不知道为什么生成类图的时候输入int他自动给我改成int[]类型,特此说明。这个值保存的是该题目的题号,方便后续在题目数组中查找对应题号的题目时能够找到对应的题号。
3.属性content保存的是题目的内容,创建这个属性是为了匹配和输出题目内容。
4.属性state是一个Boolean类型的值,它表示这道题目的存在与否,如果该值为true,则这道题目未删除,存在,若这道题目被删除,不存在,则该值为false。
5.属性score保存了这道题目的满分分数,后续计算题目分数的时候就需要从这里取得该值。
6.属性standardanswer保存了这道题目的标准答案,这样可以与后续试卷上的作答用equals进行比较,对题目的正误进行判断
这些属性均设为protected类型,方便在子类中对相应的值进行修改,当然,设置成为private类型也可以,但是想要在子类里修改或者取得这些函数,就得使用对应的setter/getter方法,非常不方便。
方法:
主要包括了一个有参构造方法,一个无参构造方法,属性的getter/setter方法。
judgement方法:这个方法是用来判断题目是否正确的方法,需要输入7个参数,这里的设计是有问题的,应该额外建立一个判断函数来判断每道题的正误,并放在一个答题结果类里。
这个类的设计上还是存在很多问题的,比如出现了不该属于这个类职责的judgement方法。而且后续对多个该类类型的数据保存也使用了错误的方法。后续是使用了一个Questions类,内部保存了一个Question类的数组,对数据进行存储。实际可以使用List
测试点未通过分析:
上面提到了这次作业有两个测试点未通过,经过反复查找问题,发现是主函数中有一个判断的逻辑错误导致的。
首先分析主函数中的问题,主函数中没有做到只有输入输出,其中添加了大量正则表达式判断,分割,输入数据判断。这种是错误的,应该加入一个Split类进行分割,加入一个Agent类进行输入数据判断,在Agent类中再调用判断类对正则表达式进行判断,输入数据的判断。
最后发现问题是主函数中对没有数据输入的时候存在逻辑判断错误。面对这种情况下,我的思路是new出一个简单题类型,content为none,判断正误的时候判断到none就知道这道题不存在,是一个空题。
7-1 家居强电电路模拟程序-1
这道题是一个全新的模拟场景,模拟一个家居电路。主要的类就是一个父类Electricdevice类,所有的用电器类都继承自这个类,对于串联电路,也把它看成一个特殊的用电器,同样继承自此类。 对于输入处理,用一个Information类保存。
整体思路如下:用正则表达式处理输入,将分割后的信息储存。由于没有异常信息输出,所以没有对应的异常输出处理。再从储存的信息中提取电路电器信息,new出每条信息对应的电器类,再遍历操作类信息,对电器的状态进行改变,这样就保证了整个电路的信息与要求对应。将new出的电器放入一个Electricdevice类型的数组中,用List
对Agent类进行简要分析:
属性:
Agent类中只有一个串联类类型的电路属性,用来保存电路信息
方法:
Agent类中有一个无参构造方法,有添加电器方法,获取电路信息方法,打印电路信息方法,和排序方法。
其中这个排序方法我采用的是“暴力排序法”,即对比每个电器的类类型名来进行排序,目前没有想到更好的方法。
在这个类的设计上,其实可以免去createSystem类,使用getRoad().add()方法即可。
对Electricdevice类进行简要分析:
属性:
1.name是电器的名字,后续排序的时候需要使用
2.id是电器输入的次序,排序也需要使用
3.V1, V2是输入电压和输出电压,这两个电压的差值即为电压差U,这里设定了V1初始值为220,V2的初始值为0,其实是错误的,因为串联电路未接通时候电压为0。
方法:
主要就是getter/setter方法,有参构造方法和无参构造方法,还有一个打印信息的方法,无特别之处。
这个类的设计存在缺陷,如缺少一个表示电器状态的state属性,这样在后续的判断中需要用for循环对电路里的每个电器进行操作,非常不方便,而且浪费资源。
测试点分析
在这个测试点卡了很久,后续改正发现是没有按照KFLBRD这个字母顺序输出,而且有可能有多个开关,没有考虑这两种情况。在写完多个开关判断的时候,我就发现了代码结构的问题,所以在满分后我又修改了代码,在Electricdevice类中添加了state这个属性,来表示这个用电器的状态,控制类电器的初始值为false,用电器类初始值为ture。然后在串联电路中添加了一个方法,使得电路可以判断整条电路的状态。同时我还添加了电阻,电流两个方法,这对后续迭代会有帮助
7-1 家居强电电路模拟程序-2
这道题的主体思路与上一道题大致相当,除了多了一个落地扇类,多了一个并联类,并无本质区别。沿用了上一次的代码后,我删除了Agent类,添加了一个输入处理类,一个并联电路类,就基本完成了整个设计,这是我第一次几乎做到了开闭原则,第一次体会到段老师上课说的对扩展开放,对修改关闭的好处。
这里提下一个卡了很久的测试点:
因为排序的代码我直接引用了上一次的,所以在遇到有落地扇的输出信息排序时候,全错了,当时找了好久,最后发现是排序的问题,除此之外都是一遍就过了。所以在写一道题目的时候,一定要先反复读题理解题意,就像段老师说的,先要做好需求分析,如果需求分析做不好,那肯定是不行的。就像我这个代码,其他都没问题,完全是符合要求的产品,结果因为漏掉一个需求,导致运行结果出错。
分享一些踩坑心得
1.主函数一定不要写很多处理输入输出信息的代码在里面,一定要用类跟方法实现处理输入输出的功能,主函数只能放输入和输出的代码,其他一概不要放在里面。因为如果大量代码在主函数里,会导致后续逻辑极度混乱,可能会出现改了一个bug,出来三个bug,而且你还没法找到。比如答题判断程序-4,我就是因为有大量处理输入输出信息的代码在主函数里,导致我最后没找到程序错在哪里,最后还是在与同学讨论,在代码不同位置插入断点进行反复调试后才发现的bug——一个本该写在for循环外面的if/else写在了for里面,导致没有判断输入为空的情况。究其原因,就是因为在主函数里用了多重循环嵌套处理输入输出,最后看错了这段代码要放的位置。其实些循环,完全可以放在多个不同的类和方法中进行实现。
2.使用继承的时候一定要考虑子类是否有某些父类没有的方法。如果子类有父类没有的方法,一定要谨慎使用父类数组储存子类对象。因为可能出现:在父类的数组里,我要调用某个位置上储存的子类对象的方法,但是父类不能调用子类里有而父类里没有的某个方法。我在家居强电电路模拟程序中就遇到了这种情况,最后解决的办法就是在父类中添加一个与要调用的子类方法同名的空方法,把子类中的方法看作是对父类方法的复写。但是这种解决方案不符合继承与多态的基本原则,所以后续要考虑修改。
改进建议
我觉得目前我对继承与多态的运用还不算非常合理,在后续的练习中,我还需要对抽象出的父类的合理性进行优化,可以合理地使用抽象类对父类进行表示,有时候也可以添加一些接口,进行实质上的多继承。
总结
通过这三次作业,我进一步了解了面向对象程序设计的“设计”二字,在对程序的结构设计中,我提升了自己的设计能力,现在逐步开始能够遵循开闭原则,单一职责原则,主函数中比较简洁,只有输入和输出,这是我从这三次作业里学到的。后续我也要在代码中加入接口,抽象类等,学以致用。对于pta的题目,我认为难度非常合理,要是老师能够对有些较难的测试点进行统一提示就更好。