OO第三阶段习题集总结
一、前言
题目集七主要涉及继承、多态性使用方法,ArrayList泛型的应用方法和Comparable接口及泛型的应用。7-1在题目指导书中给出了类图和主方法源码,分析指导书中的需求并严格按照单一职责原则进行编程,最终得到的程序类图与参考类图一致。难点在于ArrayList泛型动态数组的使用,用于几何元素的存储、计算和排序,题目难度适中。7-2则是沿袭7-1,将最后得出的结果按照几何图形类型进行分组,对每组内的图形按照面积值降序排序并输出;求出每组内图形面积的总和;求出所有组的面积总和的最大值并输出。在7-1的基础上,在DealCardList类中增加对几何元素的分组方法和排序计算方法。按照指导书中的需求,一步一步分析,可很快得出最终结果,难度适中。
题目集八、九则是关于银行 ATM 机的模拟程序,能够完成用户的存款、取款以及查询余额功能。两次题目集的代码量都是比较多的,并且难度较大,耗时较长。难点在于分析实体类和业务类的关系,明确正确的层级关系;还有ArrayList泛型和迭代器的使用。题目集九还加入了贷记卡的透支取款,和跨行取款手续费的自动扣除功能。题目更加复杂,但有过上次ATM基本功能开发的经验,题目集九虽然码量大,但通过仔细研究分析指导书中给出的需求,还是能够独立完成并实现贷记、借记卡的存取款功能,得出符合要求的结果。
经过这三次的题目集训练,感觉能力得到了很大的提升。通过题目集七的练习,熟练地掌握了抽象类的继承和多态,还有涉及到Comparable接口的使用,体会到单一职责原则和开闭原则带来的好处,并且在编码过程中一直本着这两个原则进行。尤其是经过ATM模拟程序的练习,对类与类之间的关系更加清晰,同时可以熟练地掌握ArrayList泛型的使用和迭代器的使用。
二、设计与分析
(1)题目集七两道题目的递进式设计分析总结
7-1
圈复杂度测试:

类图:

题目要求设计卡片大小排序游戏,求出各卡片的面积大小,然后将卡片按照其面积值从大到小进行排序,同时求出所有卡片的面积之和。根据指导书给出的类图,首先创建好Shape抽象类,写好相应的抽象方法,最后的ToString中写入输出格式。再将每一个几何图形实体类用Shape抽象类继承,写好相关几何图形的私有属性,根据几何图形的性质重写Shape抽象类中的getArea和validate方法。Card 类需要实现 Comparable 接口中的 CompareTo()方法,首先单独写一个Comparable接口类,用Card类来继承。在Card类中重写CompareTo方法,直接用对象进行排序,相比用for循环比较代码跟简洁方便。Main类题目中有源码直接使用,其中包含有输入检测的方法, ArrayList<Integer> list = new ArrayList<>();创建了图形种类代码的列表,用来存储代表几何图形种类的0-4整数。接着通过DealCardList dealCardList = new DealCardList(list);将输入的数字传入DealCardList类中,以便进行图形种类后续的数据输入判断。DealCardList类首先对Main类中传入的整数列表进行判断分类,同时创建Card列表来储存输入对象的相关数据。此处对于list列表中的数字判断运用Switch语句,以降低程序的圈复杂度。此时数据录入完毕,然后通过validate方法进行输入数据的检测,若数据有误,输出相应的语句;若数据无误,在cardSort方法中运用双循环对几何图形的面积进行比较排序:比较:cardList.get(i).compareTo(cardList.get(j + 1));排序:Collections.swap((List<Card>) cardList, i, j + 1)。最后,在getAllArea方法中,对所有的面积进行求和,获得allArea全部面积。在showResult方法中,根据指导书中给出的测试结果进行排版,使格式与需求相同。运用测试数据进行测试,数值结果与格式均无误。通过PowerDesigner得出的类图与指导书中的类图基本一致。由于严格按照单一职责原则编程,圈复杂度较低。
7-2
圈复杂度测试:

类图:
.
7-2沿袭7-1的代码,增加了图形类别的分组,和图形分组内部的排序,最后还要求各组中面积之和的最大值输出。为了实现分组和组内排序,需要改动的只有DealCardList类中的一些方法,其余类不做改动。
DealCardList类中增加以下列表:
ArrayList<Card> circle = new ArrayList<Card>();
ArrayList<Card> rectangle = new ArrayList<Card>();
ArrayList<Card> triangle = new ArrayList<Card>();
ArrayList<Card> trapezoid = new ArrayList<Card>();
将每个几何图形创建一个列表动态数组以储存相同几何图形的数据,同时将图形按类别分组。输入时同时将图形数据存入cardlist列表和相应图形列表中。进行排序时,同7-1运用compareTo按照图形类别在组中进行比较,按照相应的格式进行输出。在showResult方法中, "The original list"直接运用cardlist列表进行输出;"The Separated List"按照指导书给出的顺序,用相应的列表逐个类别输出;"The Separated sorted List"则可用separatedSort();方法进行输出。最后获取每组中最大面积和,使用了数组比较,将每组获得的面积之和存入数组中,然后通过for循环比较,得出最大面积和,直接输出。最后,上数据进行多次验证测试,需求全部得以实现。
(2)题目集八、九两道ATM机仿真题目的设计思路分析总结
8-1

这道题首先要搞清楚每个实体类之间的关系,以便建立相应的列表关系。银行账户是属于用户和银行的,银行卡是属于账户的,银行账户和ATM机又属于银行,银行属于中国银联。用户的每个账户都拥有固定的余额,每个账户下的每张银行卡都设有密码。上述类均为一对多的关系。
至此,所有实体类的关系已经梳理清楚,开始写实体类:在ChinaUnionPay类中写ArrayList<Bank> bank = new ArrayList<>();ATM类中写其私有属性机器编号;Bank类中写ArrayList<ATM> atm = new ArrayList<>(); ArrayList<Account> account = new ArrayList<>();和私有属性银行名称;Account类中写ArrayList<Card> card = new ArrayList<>();和其私有属性银行账号和余额;User类中写ArrayList<Account> account = new ArrayList<>();和私有属性用户姓名;Card类中写其私有属性卡号、密码。实体类创建完毕。
接下来创建业务控制类DealDate类,在该类中创建相关方法录入用户银行账号相关信息:setUsers()中创建user对象,根据用户姓名依次将银行账户和卡号及密码信息录入account和card列表中,最后将User类的对象存到User列表中;setUnionPay()中创建chinaUnionPay对象,将两个银行信息录入Bank列表中,最后将ChinaUnionPay类的对象存入ChinaUnionPay列表中;setBanks()中创建bank对象,将银行名称、下属账户及银行卡和ATM信息录入account,card,atm列表中,最后将Bank类中的对象存入Bank列表中。所有信息做好了初始化。
在主方法中,对于输入的信息作为字符串储存,用while循环入口为判断输入是否为结束符号"#",进入while循环后,将字符串根据空格拆分成若干字符串数组,此处用到了正则表达式的匹配:String[] index = line.split(" +");,根据字符串数组的每一部分,做输入错误处理、结果计算和输出内容格式化。其中输入错误处理在DealDate类中用迭代器进行列表遍历,遍历整个列表查找是否存在银行账号,银行账户的余额,卡号、密码或存取款ATM机是否与开户银行相同,以达到纠错的作用,提示相关信息。迭代器提供一个统一的方法遍历对象,不用再考虑聚合的类型,使用一种方法就可以遍历对象。用到迭代器的有isExistCard、findBalance、findAccount、findName、findBank、isPassWord。不过每个聚合对象都要一个迭代器,会生成多个迭代器不好管理类。最后,在DealDate类中写了一个money方法进行余额的计算,m为要存款或取款的金额,由于存款输入数值为-xxx.xx,取款输入数值为+xxx.xx,所以可用一个公式a = findBalance(cardNum) - m;来表示存取款后余额的计算。
至此,程序完成,上数据测试与需求中的结果一致。
9-1

本题重点考察继承、多态及抽象类的应用。沿袭8-1题目,增加了用户新的用户、银行贷记账户和信用卡,信用卡透支取款时需要收取一定的手续费,透支取款手续费由中国银联统一规定为 5%,最大透支金额均为 50000 元。允许进行跨行存取款,但需要根据ATM 机隶属银行决定跨行业务手续费收取比例。其余需求与8-1相同。
这次代码直接在题目源码上进行修改:在Main类中向相应列表中进行新用户数据的录入补充和输出格式的修改;WithDraw类中的showResult方法进行输出格式的修改;withDraw方法中关于校验取款金额是否大于余额和校验是否为跨行取款的判断去除,放入CreditAccount类和DebitAccount类中结合跨行业务手续费和透支手续费一同判断处理。
由于贷记账户和借记账户有不同的业务功能但又有多种相同属性,将原有的Account类作为AbstractAccount抽象类,让CreditAccount类和DebitAccount类继承,实现多态,提高了代码的拓展性和维护性。抽象类中加入balance抽象方法,让其他两个子类根据各自属性进行重写。重写balance方法中包括有跨行判断和余额判断,根据跨行行为和取款金额分多种情况动态计算余额。
代码中将GetBalance、WithDraw、ValidateData单独作为一个类以实现获取余额、校验、计算功能,严格遵循了单一职责原则编程,降低类的复杂度,提高系统的可维护性。需求功能得以实现。
三、踩坑心得
(1)题目集七中,发现用toString方法输出一直有误,为地址值:

后经花费大量时间排查得知,是编码过程中一个方法名少打了一个字母:

犯这种错误确实很不值得,因此浪费了很多时间,要提高打字的准确率,写完一个方法一个类要回头检查,另外加注释备注,检查时会方便省时。
(2)对于Comparable接口不熟悉,理解不透彻,导致面积排序不正确:


后经过查阅资料弄明白了Comparable接口的作用:比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。修改后可得出正确结果:


(3)在7-2中的输出结果中有中括号,代码中刻意用println输出"[]",但其实在getShape()后加.toString()即可自动输出"[]",不必再刻意输出,降低代码的重复和复杂度。

(4)7-2的需求是在最后计算每组面积之和并需要找出最大组的值。开始编写时没有认真分析,只是草草写成了找出每组中面积的最大值,提交时发现"三种卡片"测试点有误,又继续看指导书发现方法逻辑错误。改正后可得出正确结果。

(5)分析ATM模拟仿真程序时,开始没有理清楚每个类之间的关系,关系混乱没有思路。经过反复研究指导书中的需求,理清各个类之间的关系。还有在匹配检查账号卡号密码时,用到很多if-else语句去对比,过程很繁琐导致最后写不下去。尝试老师讲的迭代器,进行列表遍历,在不断尝试中成功了。

对于卡号是否存在的检验
(6)题目集九中,主要是在开始创建抽象类时没有弄清楚借记账户和贷记账户的关系,后把原有代码中的Account直接变为抽象类,另创建两个账户子类用于继承。原有代码中withDraw类中的遍历检验方法中有关是否跨行和余额是否足够的判断放入账户子类中分别判断,存取款余额计算方法在第一版代码中没有考虑全面,导致信用卡余额超过-50000而不报错,或者信用卡经过不同ATM机多次取款后余额计算不准确。后根据需求把每种情况都列出来,跨行或不跨行,是否为透支取款,是否既为透支又为跨行。慢慢都去修改算法,但用了很多if-else语句去判断,方法比较繁琐。最终通过了测试。从此认识到在编程开始时要把需求理清楚,想清楚写什么后再动手,而不是写完后想写的是什么。

求余额的部分算法
四、改进建议
(1)总体来讲还是在编码过程中要养成写注释的好习惯,后期进行总结分析时有的代码会看不懂,需要花费很多时间去梳理。编码过程中要细心,不要因为打错字母而犯错,浪费时间去排查。写一道题目是首先要分析清楚需求再动手编程,磨刀不误砍柴工,不然编码过程会很混乱且效率不高。
(2)关于代码的圈复杂度,在7-1和7-2中,保持的相对较好,最大复杂度都在10以下。但在8-1和9-1中,有个别类的复杂度一度超过了10,原因还是算法不够精简。例如8-1中的Main类,在判断输入账户数据信息是否存在或正确的方法中,几乎都是if-else语句进行判断,而且有多重嵌套重复,导致复杂度升高。9-1也是如此,两个账户子类中关于跨行和余额的判断用到了大量的if-else语句嵌套很是复杂度很高。后期尽量要减少此类情况的使用,想办法用更精简的方法去提高代码质量。
8-1

9-1

(3)编程过程中要时刻注意遵循单一职责原则和开闭原则,题目集七的两道题做的还好一些。但在题目集八、九中,有些地方没有做到单一职责原则,例如在8-1中用户的数据列表都在DealData类中,但Dealdata类中主要是用来进行数据检验的类,可以重新创建一个数据初始化类写这些列表。
五、总结
这三次题目集最大的收获是深刻地理解了继承、多态及抽象类的应用;ArrayList泛型的应用,Comparable接口及泛型的应用,单一职责原则的应用和"开-闭"原则的应用,并且都能够熟练应用。通过题目集七的两道题,体会到继承多态带来的好处,降低代码的耦合度,提高了代码的复用性,使类与类之间产生联系。通过习题集八、九ATM仿真系统的练习,对于类与类之间的关系更加熟悉,懂得了分析类之间关系的重要性。编程过程中用到的单一职责原则也使后续调试修改代码更加方便,不会因为类中方法而混乱从而建立稳定灵活的系统。开闭原则对扩展开放,对修改关闭。在程序需要进行拓展的时候,不去修改原有的代码,实现一个热插拔的效果,使程序拥有良好的扩展性,易于维护和升级,编码过程思路也更加清晰。最重要的是编程过程中要有绝对的耐心去面对出现的各种错误,遇到问题冷静处理分析,多方面寻找原因去解决。

浙公网安备 33010602011771号