OO第三阶段作业总结
目录
(1)第七次作业
知识点:继承和多态的应用,ArrayList泛型,对象数组,对类成员使用Comparable或Comparator接口排序,单一职责原则,“开-闭”原则。
代码量继续增加,类之间关系愈发复杂繁琐,难度适中,主要侧重于对象数组和新的排序接口的运用,以及学习类间关系的设计。
(2)第八次作业
知识点:类之间关系设计,对较复杂需求的分析和自行构建解决方法的能力,对象数组,用Iterator迭代器自上而下和自下而上的搜索。
看起来知识点不多,且是前面已经出现过的堆砌,题量也只有一题,但这基本是首次让我们完全自行构建完整的解决方案,没有给出类图和大量提示,完全靠自行分析。同时,难度更进一步,需求比以前复杂得多,最终画出的类图前所未有地复杂,代码量也空前庞大,但是极大地锻炼了分析设计能力,和灵活运用所学知识的能力。
(3)第九次作业
知识点:继承与多态,其余同第八次。
看起来只是加了继承与多态的要素,实际上难度再次提升,加入抽象类和方法后,整个类间关系的复杂度又大大增加,如果对上次作业没有深刻理解的话很容易走入各种误区,难以完成任务。难度达到了本学期的巅峰,是本学期知识的大综合,也初步体现出了面向对象编程的精髓。虽然老师这次给出了提示源码,以让走弯路的同学规避错误,但难度丝毫不低。我由于更熟悉自己的代码,遂用上次作业的代码快速改进。
在此挑选一些个人认为比较有意义去分析的题目,拿出来分析和总结,既是对我个人能力的进一步提高,更是让这些题目能起到更大的作用,同时还可造福看到这篇Blog的初学者们。
(1)第七次作业 递进式分析总结
掌握类的继承、多态性使用方法以及接口的应用。详见作业指导书 2020-OO第07次作业-1指导书V1.0.pdf
输入格式:
- 首先,在一行上输入一串数字(1~4,整数),其中,1代表圆形卡片,2代表矩形卡片,3代表三角形卡片,4代表梯形卡片。各数字之间以一个或多个空格分隔,以“0”结束。例如:
1 3 4 2 1 3 4 2 1 3 0
- 然后根据第一行数字所代表的卡片图形类型,依次输入各图形的相关参数,例如:圆形卡片需要输入圆的半径,矩形卡片需要输入矩形的宽和长,三角形卡片需要输入三角形的三条边长,梯形需要输入梯形的上底、下底以及高。各数据之间用一个或多个空格分隔。
输出格式:
- 如果图形数量非法(小于0)或图形属性值非法(数值小于0以及三角形三边不能组成三角形),则输出
Wrong Format
。 - 如果输入合法,则正常输出,所有数值计算后均保留小数点后两位即可。输出内容如下:
- 排序前的各图形类型及面积,格式为
图形名称1:面积值1图形名称2:面积值2 …图形名称n:面积值n
,注意,各图形输出之间用空格分开,且输出最后存在一个用于分隔的空格; - 排序后的各图形类型及面积,格式同排序前的输出;
- 所有图形的面积总和,格式为
Sum of area:总面积值
。
该题
该题代码已经实现了题目要求的类关系设计,类图如下所示:
可以看到已经完美还原了题目要求的关系设计。
使用SourceMonitor分析如下:
最大复杂度小于10,在基本标准内,介于程序本身就不需要实现较复杂的算法,这个值还算正常。
掌握类的继承、多态性使用方法以及接口的应用。 具体需求参考作业指导书。
输入格式:
- 在一行上输入一串数字(1~4,整数),其中,1代表圆形卡片,2代表矩形卡片,3代表三角形卡片,4代表梯形卡片。各数字之间以一个或多个空格分隔,以“0”结束。例如:
1 3 4 2 1 3 4 2 1 3 0
- 根据第一行数字所代表的卡片图形类型,依次输入各图形的相关参数,例如:圆形卡片需要输入圆的半径,矩形卡片需要输入矩形的宽和长,三角形卡片需要输入三角形的三条边长,梯形需要输入梯形的上底、下底以及高。各数据之间用一个或多个空格分隔。
输出格式:
- 如果图形数量非法(<=0)或图形属性值非法(数值<0以及三角形三边不能组成三角形),则输出
Wrong Format
。 - 如果输入合法,则正常输出,所有数值计算后均保留小数点后两位即可。输出内容如下:
- 排序前的各图形类型及面积,格式为
[图形名称1:面积值1图形名称2:面积值2 …图形名称n:面积值n ]
,注意,各图形输出之间用空格分开,且输出最后存在一个用于分隔的空格,在结束符“]”之前; - 输出分组后的图形类型及面积,格式为
[圆形分组各图形类型及面积][矩形分组各图形类型及面积][三角形分组各图形类型及面积][梯形分组各图形类型及面积]
,各组内格式为图形名称:面积值
。按照“Circle、Rectangle、Triangle、Trapezoid”的顺序依次输出; - 各组内图形排序后的各图形类型及面积,格式同排序前各组图形的输出;
- 各组中面积之和的最大值输出,格式为
The max area:面积值
。
类图如下所示:
可以看到和第一题差别不大。
使用SourceMonitor分析如下:
最大复杂度包括其他数据也基本一样。
(2)第八次作业
设计ATM仿真系统,具体要求参见作业说明。 OO作业8-1题目说明.pdf
输入格式:
每一行输入一次业务操作,可以输入多行,最终以字符#终止。具体每种业务操作输入格式如下:
- 存款、取款功能输入数据格式:
卡号 密码 ATM机编号 金额
(由一个或多个空格分隔), 其中,当金额大于0时,代表取款,否则代表存款。 - 查询余额功能输入数据格式:
卡号
输出格式:
①输入错误处理
- 如果输入卡号不存在,则输出
Sorry,this card does not exist.
。 - 如果输入ATM机编号不存在,则输出
Sorry,the ATM's id is wrong.
。 - 如果输入银行卡密码错误,则输出
Sorry,your password is wrong.
。 - 如果输入取款金额大于账户余额,则输出
Sorry,your account balance is insufficient.
。 - 如果检测为跨行存取款,则输出
Sorry,cross-bank withdrawal is not supported.
。
②取款业务输出
输出共两行,格式分别为:
[用户姓名]在[银行名称]的[ATM编号]上取款¥[金额]
当前余额为¥[金额]
其中,[]说明括起来的部分为输出属性或变量,金额均保留两位小数。
③存款业务输出
输出共两行,格式分别为:
[用户姓名]在[银行名称]的[ATM编号]上存款¥[金额]
当前余额为¥[金额]
其中,[]说明括起来的部分为输出属性或变量,金额均保留两位小数。
④查询余额业务输出
¥[金额]
金额保留两位小数。
类图如下所示:
可以看到已经完美还原了题目要求的关系设计。
使用SourceMonitor分析如下:
最大复杂度为15,程序需要处理的事情略多,但仍略偏高。个人猜想复杂度出在Main类中的判断校验部分,使用了大量的if else。
(3)第九次作业
设计ATM仿真系统,具体要求参见作业说明。 OO作业9-1题目说明.pdf
输入格式:
每一行输入一次业务操作,可以输入多行,最终以字符#终止。具体每种业务操作输入格式如下:
- 取款功能输入数据格式:
卡号 密码 ATM机编号 金额
(由一个或多个空格分隔) - 查询余额功能输入数据格式:
卡号
输出格式:
①输入错误处理
- 如果输入卡号不存在,则输出
Sorry,this card does not exist.
。 - 如果输入ATM机编号不存在,则输出
Sorry,the ATM's id is wrong.
。 - 如果输入银行卡密码错误,则输出
Sorry,your password is wrong.
。 - 如果输入取款金额大于账户余额,则输出
Sorry,your account balance is insufficient.
。
②取款业务输出
输出共两行,格式分别为:
业务:取款 [用户姓名]在[银行名称]的[ATM编号]上取款¥[金额]
当前余额为¥[金额]
其中,[]说明括起来的部分为输出属性或变量,金额均保留两位小数。
③查询余额业务输出
业务:查询余额 ¥[金额]
金额保留两位小数。
该代码已经实现了题目要求的类关系设计,类图如下所示:
可以看到已经完美还原了题目要求的关系设计。
使用SourceMonitor分析如下:
可以看到,最大复杂度为14,尚可,原因大致同上。
(1)第七次作业中
7-1和7-2这两道递进的题目实际上坑点并不多,根据需求而言要做的事情比较简单直接,但不多不代表不会犯错/走弯路,主要体现在:
基于面向对象设计原则中的单一职责原则,Main类应该和处理数据的DealCardList类分开,专门处理输入信息,因此此处需要把输入的信息存储成数组的形式,传参到DealCardList类中去处理。
对于抽象类Shape:
其一是容易忘记重写抽象方法,其二是toString()方法的重写。初学者很容易忽略掉toString()的重写可以省去很多不必要的重复操作,以一次操作代多次,提高代码复用性进而提升效率。
如图,在输出的时候直接调用toString()方法即可。在平时小作业中可能体会不到,在大项目中就容易体会到其便捷。
最后是关于新的Comparable接口,首先是不要忘记实现的类即是需要排序的类,泛型<Card>不要丢,接着是排序时候需要自定义的部分不要写反了:
如图,实现的是按每张卡的面积从大到小排序。
7-2坑点与之类似,唯二值得注意的是,其一,关于输入部分,虽然已经给了提示,跨方法输入使用public static Scanner input = new Scanner(System.in);和Main.input.nextDouble();,但这种手段值得我们记住和学习。其二,新增了很多对象数组,不能漏掉add()方法,要把初始化完成的对象全部加入数组中去。
(2)第八次作业中
坑点主要体现在类的设计上。由于不给类图提示,全部要由自己做决定,第一次尝试时非常容易犯一个错误:把银行,ATM机,账户,卡的信息全部存进了一个实例对象中,在调用信息的时候又下意识去开一个新的对象,而这个新对象里面是没有内容的,由此反复在Iterator迭代器进行查询时产生空指针问题。我的解决方法是,开一个Agent类,在里面开一个银联的实例对象,再通过类间关系引申出更低层的银行、ATM、用户、账户、卡等,从头到尾都只用这一个对象,调用其中的数据来计算和显示,就不会出现上述问题。
其二,是容易搞不清这么多概念之间的抽象联系,不知道是卡放账户里还是账户放卡里,ATM机要不要做成数组,用户和账户是什么关系,不知道是要用对象数组还是只是单纯的聚合关系就够,诸如此类的错误容易导致类之间关系设计的错误,导致无法完成需求。比如,没有把银行做成对象数组和银联类关联,没有把ATM做成数组,在后面的迭代中碰到多银行和跨行判断就会吃亏。要提前做好跨行的较复杂情况的应对,做好一个用户可以在不同银行开多个账户用不同的ATM机存取款的基础结构准备。在理清关系后,一大堆用户,账户,卡的数据的输入也容易搞错层次,或者漏掉几项,导致无法还原给出的表格。
其三,对存取款余额的计算容易出错。
尤其是题目要求输入负数作为存款,加负数等于减的这一段,如果转不过弯很容易写反。事实上存取款这两个方法的算法最终殊途同归,可以合并,但没必要。
(3)第九次作业中
相比第八次,删去了存款,增加了超支借贷取款环节,以及跨行取款的环节,两个额外环节都要收相应的手续费,因此,在上次作业迭代结构基本不变的基础上,本次主要的坑点在于新的继承关系以及数值计算。
由于老师特别强调了要求,不能在最后输出结果的时候手动去判断该用普通借记账户(卡)的取款方法还是贷记账户(卡)的取款方法,因此这里必须用到多态,同一条语句,程序自己会读出不同的意思,执行不同的方法。
同一个withdraw()方法,两种账户(卡)执行的结果大有不同。
这里多态的体现实际上就是重写和动态联编,父类引用子类对象,识别到子类的重写方法自动执行。如果不用多态,第一个是极其不方便,在复用性和可维护性方面大打折扣,另一个是过不去老师的检查,不用多态被查出来很可能直接零分处理了。
关于数值计算,以下是我研究了很久写出来的计算式:
这里的重点是if嵌套,由于收手续费的方法不同,跨行与不跨行的判断和是否超支的判断要分开进行。对于每一个小算式,正负号也容易弄反。
第七次作业中主要集中在7-2题,输出时基本都是手动打的,没有用到重写的toString()方法(其自动生成功能在老师的一些要求下反而更方便,一般都带[]符号)
可以寻找更简单的输出写法,比如如何自动生成出同样的输出结果。
第八第九次的可改进的地方基本相同,首先是一大堆Iterator搜索方法中存在重复和滥用情况,可以适当精简搜索用的方法,增加需要的,去除不需要的。其次是有些类的属性不齐全或者多余,比如卡里不要有余额,ATM机应该包含银行名信息(这两个都已经修改过了),等等。最后还是抽象概念之间的关系,我目前是把所有信息全存在ATM中,每个银行都复制一遍全部ATM的信息,看起来似乎有冗余,有部分不符合题意,值得修改。
作为最后几次作业,基本上锻炼了我们编写基础OOP程序的所有核心基础技能,包括继承与多态,七大设计原则,基本的类之间关系的构建设计,把具体的需求多次抽象化解决复杂问题,尽可能不影响原有代码的基础上去进行代码重构以应对需求变更,等等。题目的难度复杂度逐渐加深,最终我们已经具备了真正解决有一定复杂度的实际问题的能力了,比如简单的ATM机存取款。经过这样的训练,我们终于能够用实践去理解之前C语言这种面向过程编程和Java面向对象编程在思路上的具体差异。解决一个问题,OOP给出的思路就是抽丝剥茧,有条不紊地张开,一层层去抽象,以模块化的方式逐步解决问题,各个模块可随时装载卸载,虽然代码看上去复杂冗长,但是细看各个模块的组合十分有条理和逻辑,并没有哪行代码是真正多余的。因此OOP尤其适合应对大型工程,可读性、可复用性和可维护性都远高于传统一步到位的面向过程编程。
在这样的过程中,尽管难度代码量都增加不少,但我这几次作业都以满分通过,改变了之前总是拖拉,压线交,不能满分的状态,对于自身而言也算是解决了时间安排上的问题,自身能力也得到了飞跃性的提升。在课程即将结束之际,我必须得感谢OOP这样一门奠基石般的课程,锻炼了自己的方方面面,为我今后的发展打下了坚实的基础。