航空货运管理系统迭代分析
前言
从总体上来看,两次题目集围绕“继承与多态”这个主题,涉及到了抽象类、继承、容器类(ArrayList、LinkedList)等使用,其中迭代的航空题目要求设计原则上必须要做到单一职责原则、里氏代换原则、开闭原则和合成复用原则等,以体现学生对面向对象编程设计思想的理解与运用,体现面向对象编程的核心思想。两次的题目集题量并不是很多,但是却都有一定的代码量,展现了对学生综合编程能力的训练,这两次的题目集后面迭代的航空货运管理系统题目虽然没有之前迭代的电梯的题目那样有比较复杂的算法,但却不像上次那样有类图的设计加以指导,类的设计完全由学生自己完成,大大增加了设计上的难度,需要学生具备综合的编程能力。总体而言,本次题目集难度较大,需要仔细的设计类间的关系。
设计与分析
因为题目集其他题目已经提供了相应的类图,本次主要分析迭代的题目“航空货运管理系统”和雨刷的题目
航空货运管理系统
题目集八
题目分析:
题目要求通过输入客户信息、货物信息、航班信息以及订单信息,生成相应的运货订单,在算法上主要涉及一个计费重量、计费运费的运算,并没有太大的难度
设计的类图如下:

类图设计分析:
设计了Order(订单类)为抽象类,而Order1为继承于Order(订单类)的一个子类,Goods(货物类)为抽象类,Goods1为子类继承于Goods(货物类),抽象类PaymentMethod(支付方法),其子类有WechatPayment(微信支付类),AliPayment(支付宝支付类),还有就是一个顾客类。从整体上来看实现了一定程度上实现了单一职责原则、里氏代换原则、开闭原则和合成复用原则。
类的设计不足:
订单类与其他类(货物,航空等)不应该有直接的联系,合理的设计应该加一个代理类,用于处理订单类与其他类的关系,以及对订单的其他操作。添加订单管理类,有助于实现迪米特法则(LoD),减少代码间的耦合性,更好的实现单一职责原则(SPR)。
SourceMonitor的分析如下:




SoureMonitor分析结果的解释与总结:
主要存在的问题:
- 从雷达图来看,注释语句较少,占比仅为3.9%,不在图中绿色显示的良好范围内,这不利于代码的理解与可维护性
- 方法调用数较少,说明代码模块化程度较低,可能存在大量内联逻辑,说明代码在单一职责(SPR)原则上实现的并不太好,根据之前的类图分析,推测可能是没有设置代理类的缘故
- 平均复杂度为1.24,比较低,说明代码的实现逻辑比较简单,虽然代码过来测试点,可能存在隐藏的问题
最后,从其他方面如平均深度、最大复杂度等都处于良好的范围内。总结起来代码可以从以下几个方面做到改善:
- 重构长方法,将大的复杂的方法拆分成小的方法,提高代码可维护性,从而更好的维护单一职责原则
- 将部分功能拆分为独立类或工具类,降低类的耦合度,比如可以把计算费率、运费和重量的方在一个工具类里面,便于管理与测试
- 实现命名规范。一些类、方法、属性的命名并不规范,比如startflight等,会造成一定的歧义,不利于代码的维护性与可读性
- 增加代码的注释
心得总结
由于这次迭代老师没有给类图,所以类的设计都是由自己完成。第一次迭代的时候我还没有明确到底怎样设计类之间的关系,为达到老师那四个要求,便把订单类、商品类、支付类都设计成抽象的类。尽管代码通过测试点很容易,代码在一定程度上也达到了老师要求的设计原则,但通过工具的分析与老师的讲解,发现类的设计并不合理,实际上仍有较大的复杂度,仍存在很多的漏洞,没有真正实现面向对象编程的原则。所以在做题前,应先仔细读题,想好要设计什么类,理清类间的关系以及怎样设计类间的关系最为合理是至关重要的一步,不能只为了过测试点而去编程。
题目集九
题目分析
题目集九的航空货物管理系统题目基于题目集八,在输入输出上并没有做出大的改变,而在顾客的类型上分成了个人用户和集团用户,货物分成加急货物、普通货物和危险货物,主要涉及类的关系设计。
类图设计如下:



类图设计分析:
与上一次题目集相比,支付方法多了现金支付,客户的类型分为个人用户和集团用户,货物类型分为加急货物、危险货物和普通货物。总体而言还是涉及类的设计,与上次相比,这次类的设计把客户类设置成抽象类,但是取消了抽象的订单类。
SourceMonitor的分析如下:




SourceMonitor分析结果的解释与总结:
主要问题:
- 平均复杂度为1.24,小于良好范围,说明代码的总体复杂度不高,代码逻辑分布可能不均衡,可能存在少数方法承担过多职责(违反单一职责原则),而其他方法功能过于单薄。
- 平均每个方法的语句数为2.85,数量过低,可能是因为过多的get、set方法导致的
其他的数据均处于良好的范围内,与上次相比,代码的注释占比达到了8.2%,与上次相比有明显的提高,但是仍较低,说明这一部分仍有待提高,需要增加代码注释的数量。
心得总结
总体而言,这次题目的条件比较明确,所以在设计类间关系的时候就不会那么迷茫。主要存在的问题是老师建议合理的订单类一个再设计一个订单明细类,但是我没有写。从现实生活的角度上来讲,这样确实更合理,也避免订单里面方法过多的问题。所以以后在类的设计时,要充分考虑其合理性。然后就是老问题,要增加代码的注释。
雨刷程序功能扩展设计
题目分析:
这道题目其实在之前的题目集也做过,但是需求不同、要求也有变化,在题目集八里面为了考验同学对多继承、多态的掌握,便对这道题目进行了再次迭代,出现了一套新的雨刷系统,但要求之前的雨刷系统要保留。而这道题目同样没有类图的设计,完全由我们自己设计。(注:因为我第一次做类的继承关系的设计,不知道怎么设计,所以第一次设计的并不合理,完全违背了开闭原则的核心要点,所以这道题目做了两次设计,下面会着重比较两次设计的不同)
第一次类图设计:

类图设计分析:
第一次类图设计时,不是很清楚要怎么做,考虑到因为两次雨刷只是控制杆、刻度盘的最高档位变了还有雨刷各个挡位的最高速度改变了,于是我给雨刷的控制杆和刻度盘新增了一个最高挡位为私有属性,然后设计雨刷系统为父类,下面有第一套雨刷系统(之前的)和第二套雨刷系统(新增的)为其子类,然后Agent类来选择雨刷系统,这样以来,就要改不少的代码,我也确实花了不少时间,包括改了雨刷系统后出现的一些问题也要去改正(测试原来雨刷系统的出了问题,其实上次测试点都过了,这次也没改变测试点,显然全盘的改变出现了一些问题),修改好一会才过了测试点,显然这是一个不合理的雨刷设计,重构的同时导致了更多问题的出现。
下面是一个比较合理的类图设计:

在这次设计中,把刻度盘、控制杆、代理类都做成抽象类(Brush类不用,因为没有改变),保留了之前雨刷代理类,控制杆类、刻度盘类为对应抽象类的子类(子类分别为Agent1,Lever1,Dial1,抽象类为Agent,Lever,Dial),新的Agent2类、Dial2类、Lever2类为对应新的雨刷系统的代理类、刻度盘类和控制杆类,分别继承对应的抽象类,每个雨刷系统对应每个对应自己的雨刷代理类。这样一来,既保留了原来的,也实现了增添新的雨刷系统,真正实现了“对修改关闭、对扩展开发”,减少了代码量以及避免了重构可能出现的问题。从类图发现这样设计也更具有条理性。
SourceMonitor的分析如下:


从SourceMonitor上来看,二者在复杂度上没有什么大的区别,但是当增加多个雨刷系统时,显然第二种雨刷系统更为合理,更能应对各种各样的雨系统。
心得总结
这个雨刷功能扩展的问题使我深刻的认识到了开闭原则的重要性,当一个系统要增添新的功能时,对于已经确认无误的代码,我们不能修改,只能另外新增代码(因为新增的业务流程的形式可能是多样的,无法保证它与原来的是否十分相近,如果要修改的话可能会导致之前的也出问题),在这里,多态的重要性就体现的尤为重要,通过抽象实现多态,既能实现代码复用,还能实现扩展。对于在业务逻辑不断变化的情况下,多态能够使系统快速适应变化。在今后做题中,涉及此类型的题目应该充分考虑如何正确使用多态。
踩坑心得
题目集八
雨刷程序功能扩展设计
踩坑点1
在第一次的雨刷系统设计时,当我输入
1
1 3 3 3 3 3 3 0
输出的是

提交代码显示

原来的雨刷的刻度盘的刻度最大就为五,所以我去Dial类里面检查代码,发现这段代码:
1 //升刻度 2 public void dialUp() { 3 if(this.pos < 5){ //不是5,应该是this.highpos 4 this.pos ++; 5 } 6 }
根据之前的类设计,这里的“this.pos<5”应该修改为小于“this.highpos”,修改后就正确了
总结:学会设置测试数据对于寻找代码的漏洞很重要
踩坑点2
当我把这道题雨刷重新做一遍后,重新设计一下后,发现有三个测试点一直过不了

想了好久也不知道错误出现在哪里,后来当我输入
1
2 2 0时,却输出了

按理来讲刻度盘不应该升刻度,升刻度应该是3的操作,所以当我检查Main类里面主的方法时,发现这一段代码:
1 while(n != 0){ 2 3 switch(n){ 4 5 case 1://Lever up 6 7 agent.getLever().leverUp(); 8 9 break; 10 11 case 2://Lever down 12 13 agent.getLever().leverDown();//这里少了break 14 15 case 3://Dial up 16 17 agent.getDial().dialUp(); 18 19 break; 20 21 case 4: 22 23 agent.getDial().dialDown(); 24 25 break; 26 27 case 0: 28 29 System.exit(0); 30 31 }
case2的时候没有加break,导致输入2的时候又向下执行了case3的 "agent.getDial().dialUp() ",才跳出switch
总结:当测试点过不去的时候要学会合理的设置边界测试数据,这样可以迅速找到错误的地方。
题目集九
点线面问题再重构
踩坑点1
当我第一次提交的时候,出现

显示删除列表元素错误,显示为非零返回,推测可能是访问了空列表,所以我输入数据:1 1.2 2.3 4 2,eclipse报错,显示访问了空列表,发现在主要在154,106行代码出了问题

所以检查代码,发现在106行这一部分:
1 public void remove(int index) { 2 3 if(!this.elementsList.isEmpty()) {//判断条件错误,没有判断index是否合法 4 5 this.elementsList.remove(index-1); 6 7 } 8 9 }
并没有判断输入的index是否超过列表的大小就执行了remove的操作,所以修改为:
1 public void remove(int index) { 2 if(!this.elementsList.isEmpty()&&index<=this.elementsList.size()) { 3 this.elementsList.remove(index-1); } 4 5 }
就没问题了
总结
- 通过这几次的踩坑经历,我发现学会设置测试点很重要,对于我们发现代码那些忽略的错误、漏洞很有帮助
- 写代码的时候一定要仔细
- 学会使用编译器编写代码,在一定程度上也能避免向上面break没写的错误(语法错误编译器会自动显示,在pta上写不会显示出来)
改进建议
- 加强对继承多态和开闭原则的学习,通过雨刷和航空运货的例子发现这一块掌握的并不是那么好,要深入理解开闭原则的要义,要真正理解“对修改关闭,对扩展开放”这句话的含义
- 代码注释增强。代码的注释仍然不够,需要在平时编程中养成这个习惯
- 命名规范问题。一些属性、方法名、类名得命名还是不太规范,需要在日常学习中学会合理命名
- 学会合理设置类及类间的关系(先要充分了解面向对象编程的七大原则)
- 学会熟练的使用编译器编程,会大大提高编程效率
总结
通过这几次的题目集,我学到了以下内容:
- 通过这两次题目集的迭代,我学会了如何使用继承、抽象实现多态,深刻领悟到了开闭原则的内涵和重要性
,对于面向对象原则中的迪米特法则、单一职责原则等也有了深刻的理解
2.自己设置测试点检查错误的能力也有了较大的提高
需要进一步学习的地方:
- 面向对象的七大原则还需要多加学习,这样有助于合理设计类及类间的关系
- 在类的设计时要考虑现实情况,这样设计出来的才比较合理
- 提高个人的编程能力。编程的速度还是太慢,还需多练
课程建议:
对于PTA的题目可以多设置几个测试点,最好涉及多个方面的,这样可以提高同学们自己设置测试点去测试的能力
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号