题目集7~9的总结性Blog
(1)前言:
题目集7-9题目量不大,第7次2题(图形卡片排序、分组),第8次、第9次各为1题(ATM机类结构设计)。题量不大,但难度较高(尤其是后两次题目集)。这三次的题目集难度并不在算法上,而是难在知识点,如实现多态(这三次题目集大量使用),接口(在这四道题目仅仅是用到,未深入),还有尽可能实现开闭原则和单一职责原则。题目集7-9的知识点有接口、多态、继承、ArrayList的使用、Collections(排序)的使用、Iterator(迭代器)的使用、开闭原则和单一职责原则。
(2)设计与分析:
题目集7(7-1):解题思路:先构建好各个图形类,然后再将输入的代表图形的数字一一用ArrayList存储,存储前判断输入的数字是否正确,若正确,进行下一步,反之输出错误。再根据存储的数字把相对应的数据进行计算并将对应的图形作为对象存入ArrayList里(当然,存入之前已经是确认数据无误的)。
以下是生成报表:

由图可知我这一题的代码平均复杂度即深度还算正常,即使是Triangle类有7的复杂度,仍然在正常范围内。就是DealCardList类的平均复杂深度严重超标(或许是调用的对象过多,但这不可避免)。
类图如下:

由图可知,Main类调用DealCardList类,DealCardList类调用Card类,Card类和Shape类是聚合关系。Circle、Rectangle、Triangle、Trapezoid四个类均继承于Shape类。DealCardList类是一个工具类,用于存储各图形数据,和检测各数据的合理性。
题目集7(7-2):解题思路:和题目集7(7-1)类似,就是比7(7-1)多了多个相同图形面积排序和总面积排序,通过创建多个ArrayList动态数组来实现分别存储相同图形的数据。
以下是生成报表:


由图可知,我这道题的代码平均复杂度和深度还行(除了DealCardList类)。DealCardList类平均复杂度高的原因是用的大量的for-each语句来遍历相加,以及图形面积值的输出。
类图如下:

由图可知,7(7-2)和7(7-1)的类图相似,类与类之间的关系以及类的作用几乎没有变化的,相同部分就不再做分析。
题目集7(7-1)、(7-2)两道题目的递进式设计分析总结:
题目集7(7-1)很明显是为7(7-2)做铺垫的。7(7-1)的要求就是把图形卡片分类,计算各自的面积值,再根据面积值的大小进行排序。在输出的时候先输出排序前的面积(即按用户输入的顺序为准),再输出排序后的面积,最后输出图形总面积。7(7-2)的要求是把图形卡片分类,计算各自的面积值,再根据同类图形的面积值大小进行排序。在输出的时候同类图形先输出排序前的面积,再输出同类图形排序后的面积,最后输出最大图形总面积(每一类的图形不止一个时,求它们的和)。输出参考结果如图:

7(7-1)

7(7-2)
这两个参考输出结果比较图可以明显地看出相比于7(7-1),7(7-2)的格式要求和排序方法是基于7(7-1)而来的。排序方法并没有变,仅仅是改动了输出格式。7(7-1)更像是在先教会如何运用对象动态数组(就像题目中的ArrayList<Card>),等到理解后就开始把图形分类,由把“图形”作为分类的依据,改为“图形种类”作为分类的依据。这个递进,我是用多加几个ArrayList来实现的(上面放的类图明显可以看出,多了4个对象动态数组)。
注:7(7-1)、7(7-2)两题的排序均为降序排序。
题目集8(7-1):解题思路:先根据题目给的条件,按照题目的要求构建了实体类和自行添加了业务类。为实现功能,我写了两个“套娃链”,分别是“银联--银行--ATM机”和“用户--银行--账户--卡号”(比如银联有多个银行,每个银行有多个ATM机,用ArrayList实现)。然后根据缺少的我添加了初始化类和代理类两个业务类。初始化类用于初始化数据并存储,代理类则处理一切取款业务(包含非法输入判断)。
以下是生成报表:


由图可知,Agent类和Initialize类平均复杂度和深度较高。是因为Initialize类在数据存储的时候用了一些for-each循环语句,导致复杂度过高。Agent类是因为我把判断和取款都放在了同一个类里,判断是否为正确输入至少需要5个if,而取款的方法又要用到迭代器遍历,while嵌套、if嵌套不可避免。复杂度和深度自然就高了。
类图如下:

由图可知,Main类调了Agent类和Initialize类,Agent类和Initialize类均调用了所有实体类(ChinaUnionPay、Bank、ATM、User、Account、Card六个类),Bank类里有一个以ATM为类型的动态数组,User类里有一个以Account为类型的动态数组,Account类里有以Card为类型的动态数组。业务类Agent类有校验和取款功能,Initialize类有初始化数据的功能。
题目集9(7-1):解题思路:题目要求在上一次的基础上添加贷款和跨行取款功能。题目说明说了卡能否贷款取决于账户。我就把Account类改成了AbstractAccount类(抽象类),再用CreditAccount类(贷记账户类)和DebitAccount类(借记账户类)分别继承于AbstractAccount类。DebitAccount类是和8(7-1)的Account功能一样,CreditAccount类则是在Account类的基础上变动了可贷款余额(若检测到时贷记卡才会增加贷款余额)。贷款功能的实现是在Card类里加了卡的种类。跨行取款就是在初始化数据的时候在Bank类里加了跨行手续费比例。
以下是生成报表:


由图可知,Main类的语句数特多。语句多则是我把初始化放在了Main类里面。而且初始化有很多语句。ValidateData类的复杂度和语句数较多,是因为判断数据正误的话if不可少,而且判断的东西也多,所以语句数就多了。至于Withdraw类我判断了是否跨行,因为跨行的算法和不跨行有一些差别,所以导致最大复杂度稍高。
类图如下:

由图可知,所有实体类(ChinaUnionPay、Bank、ATM、User、Account、Card六个类)除了ChinaUnionPay类以外,都实现了双向链表,即可以相互查找(如通过账户可以得知用户,通过用户也可以得知账户)。GetBalance类用于获取账户余额,ValidateData类用于判断数据正误情况,Withdraw类用于取款。
题目集8和题目集9两道ATM机仿真题目的设计思路分析总结:
这两道题都有一个特点,就是类比较多。因此,每个类的功能都更加细化。8(7-1)的设计方面有两个难点,一是数据的初始化,二是如何实现从下而上的查找(如给账户找拥有者)。而且在题目说明里明确说了业务类自行添加。也就是说,像实体类只能进行添加(set、add)、删除(remove)、获取(get)的操作,而对数据进行进一步操作就必须要用业务类了。其实8(7-1)从下而上的查找是解决此题的关键。使用Iterator(迭代器)和多次while循环,可以进行遍历所有数据进行匹配,最终判断数据是否存在。9(7-1)的设计方面难点在于如何实现多态。题目说明着重写了卡能否贷款取决于账户类型,所以账户是肯定要继承于抽象类的,同时也便于再添加新类型的账户。而两种账户唯一的差别就在于能否贷款,所以可以针对能否贷款这一点来构建抽象类。至于跨行手续费(银行),和贷款手续费(银联)就只是在相应类里面加一个属性的事。
(3)采坑心得:
7(7-1):一开始我没注意Scanner要用静态,导致后面数据的输入出现了问题,在调试过程中发现后面图形卡片与图形相对应的数据不能一一输入并存储,总是会在存了一个图形卡片后停下来。下面代码中注释掉的地方就是检测是否运行到了相应case里面的语句。
1 public DealCardList(ArrayList<Integer> choice) { 2 // System.out.println(i); 3 for(int i : choice) { 4 5 // System.out.println(choice.get(i)); 6 switch(i) { 7 case 1:{ 8 // System.out.println("1"); 9 double radius = Main.in.nextDouble(); 10 Circle cir = new Circle(radius); 11 Card card1 = new Card(cir); 12 list.add(card1); 13 break; 14 } 15 case 2:{ 16 // System.out.println("2"); 17 double width = Main.in.nextDouble(),length= Main.in.nextDouble(); 18 Rectangle rec=new Rectangle(width, length); 19 Card card2 = new Card(rec); 20 list.add(card2); 21 break; 22 } 23 case 3:{ 24 // System.out.println("3"); 25 double a = Main.in.nextDouble(),b= Main.in.nextDouble(),c= Main.in.nextDouble(); 26 Triangle tri = new Triangle(a,b,c); 27 Card card3 = new Card(tri); 28 list.add(card3); 29 break; 30 } 31 case 4:{ 32 // System.out.println("4 "+i); 33 double a = Main.in.nextDouble(),b= Main.in.nextDouble(),h= Main.in.nextDouble(); 34 Trapezoid tra = new Trapezoid(a,b,h); 35 Card card4 = new Card(tra); 36 list.add(card4); 37 // System.out.println("1"); 38 break; 39 } 40 } 41 } 42 }
还有我在写排序的时候,把排序方式升按照升序排序,而不是降序排序写。因为我弄反了比较数和被比较数的顺序(粗心)。
7(7-2):这道题没掉什么坑,因为是按7(7-1)改的,坑都在7(7-1)掉完了。就是在写输出格式的时候略有点麻烦而已。
8(7-1):我在写这道题的时候,在初始化的时候遇到了难题。就是在初始化的时候一直处理不好数据存储这一块。因为我是用ArrayList存储的。但在调用ArrayList里面的数据的时候,常常会出现null的情况,尤其是在其他类里面调用,报错提示null的情况就更多了。从而导致我之后的计算无法进行(会报错null)。还有我做的从下到上的用于查找的迭代器也没写好,不能准确找出相应的数据(即无法用卡号找账户),因而也不能修改账户余额。
9(7-1):这道题是用老师给的参考代码8(7-1)改的。一开始我没弄好多态(即把所有数据都存入Account抽象类里面),把数据直接分别存在相对应类型的账户里,导致不能通过调用抽象Account类来调用,等写到最后还要写好几个仅仅换了一个类名的方法,而且还导致最后实现取款功能的时候还要根据账户类型写不同的取款方法(即使是实现相同的功能,但就是要写好几个)。还有Card类,我之前也把它分为了两种,然后代码写了一堆还是不能运行。最后重新理了一遍,才明白只有账户需要用多态,才从坑里面爬出来。

修改前 修改后
(4)改进建议:
7(7-1):感觉真没什么可以改的了。
7(7-2):在面积排序那一块相同面积排序可以试试比较器的三目运算符的写法,可以有效减少代码量。因为都是同类图形,所以不用考虑图形名称的问题。
1 card.sort((area1,area2)->{return area1.getArea()>area2.getArea()?1:area1.getArea()<area2.getArea()?-1:0;});
8(7-1):数据初始化要改。我设计的初始化调用只要出了初始化所在的方法(注意:是方法!甚至不是类),就会有几个数据出现null的情况。然后为避免这个问题,我每次使用数据都要new一个新的初始化对象来调用数据,很麻烦。而且用迭代器来找数据能找到,但不能找到它的上一级是什么(无法实现从下往上找)。这是“单向链表”的弊端。所以要把单向链表改为双向链表。
9(7-1):账户多态的那一块要做修改。我对于账户贷款余额并没有在账户类里面设定一个属性,而是作为一个数值放在取款里面。可尝试将贷款余额作为一个私有属性加到不同账户类里面去。这样就不用把余额最低值设定为负数了,设计更为合理。
(5)总结:
通过这三次题目集,我学到了开闭原则的理解,使用多态实现开闭原则,使用迭代器查找数据,双向链表的实际使用,更加深刻理解了类的继承。通过图形卡片的题目,我更深刻理解了ArrayList的使用方法,和类的继承,抽象方法的完善。通过ATM机类结构设计的题目,我学会了双向链表的使用,和数据存储的新方式。不过我在使用迭代器查找数据方面还不熟练,hasNext(),obj.iterator()等方法似懂非懂;多态的使用还是时常会弄糊涂,写抽象类的时候总是忘了只要写它子类里面的相同方法,导致在有些子类里面用不到的方法也要重写(不写就报错),看来还是要多学学。其他方面就没什么了。

浙公网安备 33010602011771号