题目集7~9总结
前言
这三次题目题量不大,但是整体难度较大,难点主要集中题目集8与题目集9的ATM机仿真内。题目集7主要考察对多态的使用,比如继承、链表的多态使用等。题目集8主要考察各类之间的关系设计,包括聚合、组合等,并需要较强的功能严谨设计。题目集9是题目集8的功能增加,知识点考察接近,但是需要更强的逻辑,比如扣除费用的顺序、计算费用的顺序等。总体而言这三次作业是之前所有作业所掌握知识点的综合考察,需要使用大部分所学知识,算是一次总体复习与进阶。
(夹带私货:后两次作业还是好难的,尤其是题目集9没有说清处扣钱的规律,需要一个一个尝试测试点,希望以后能够更清楚的描述题目与设置测试点!!!)
设计与分析
题目集7(7-1)、(7-2)的递进式设计分析总结
题目集7(7-1)图形卡片排序游戏,首先输入创造卡片,然后再依次输入各卡片的数据来计算面积,之后按面积排序后输出,从大到小排序。
根据题目分析需要先创建“图新卡片”,而对“卡片”的设计,我用的是继承的方式来制作各卡片,首先有一个抽象类Shape类,包括返回其面积的抽象方法getArea()与检验图形数据合法性的抽象方法validate(),以及将面积转换为两位小数的普通方法toString(),其余所有图形都可直接继承这个抽象类,之后实现计算面积的方法以及对数据的检验就完成了,当然其中包括对自己数据的get与set方法。根据数据的保护需要把数据设计为private类型。
1 abstract class Shape { 2 public abstract double getArea(); 3 public abstract boolean validate(); 4 public String toString() { 5 return String.format("%.2f", this.getArea()); 6 } 7 } 8 9 class Circle extends Shape { 10 11 private double radius; 12 13 Circle(){ 14 super(); 15 } 16 17 Circle(double radius){ 18 super(); 19 this.radius = radius; 20 } 21 22 public void setRadius(double radius) { 23 this.radius = radius; 24 } 25 26 public double getRadius() { 27 return this.radius; 28 } 29 30 @Override 31 public double getArea() { 32 // TODO Auto-generated method stub 33 return Math.PI * Math.pow(radius, 2); 34 } 35 36 @Override 37 public boolean validate() { 38 // TODO Auto-generated method stub 39 if(this.radius > 0) 40 return true; 41 return false; 42 } 43 44 }
之后要创建各卡片,那就使用到了多态。首先要有能存放各卡片的链表。使用链表的原因是链表能够更简便的实现数据的增加、删改与排序等操作。链表list的类型是Shape类型,由于所有图形都是继承Shape而设计的,就保证了所有图形卡片都能够添加入list。输入数据后要进行数据的切割工作(按照我的习惯是将输入的内容制作为String类型,之后通过切割实现数据的拆分,因为能够保证输入时即使输入的数据不是所需要或规定的类型不会出现系统错误,而是在我设计的程序的控制范围内的错误),这里就省略了。之后根据输入的数据进行图形创建,创建的的同时通过输入的数据赋予其边长或半径等数据。
1 //记录已使用数据数量 2 int count = 0; 3 //创建图形 4 for(int i = 0; i < num.length - 1; i ++) { 5 Shape shape = null; 6 switch(num[i]) { 7 case 1: 8 shape = new Circle(num1[count]); 9 count += 1; 10 list.add(shape); 11 break; 12 case 2: 13 shape = newRectangle(num1[count],num1[count + 1]); 14 count += 2; 15 list.add(shape); 16 break; 17 case 3: 18 shape = new Triangle(num1[count], num1[count + 1], num1[count + 2]); 19 count += 3; 20 list.add(shape); 21 break; 22 case 4: 23 shape = newTrapezoid(num1[count],num1[count + 1], num1[count + 2]); 24 count += 3; 25 list.add(shape); 26 break; 27 } 28 }
创建后按题目要求先输出一次,之后进行排序。排序使用了Collections的sort方法,对链表list排序,排序规则是各元素中getArea()即其面积的大小按从大到小排序。但是因为是保留两位小数,而flag是int类型,小数点后无法比较,所以在计算flag的时候进行了*1000的操作,让小数点后三位都能进行比较(可能不需要1000,100可能就够了,应为只要两位小数,但是我考虑到如果前两位相同而后一位可以比较的情况,不过也不应该这么设计,应该用double来比较,之后根据比较的结果给flag赋值,能够做到最准确的比较)。
1 //排序 2 Collections.sort(list, new Comparator<Shape>() { 3 @Override 4 public int compare(Shape s1, Shape s2) { 5 double flag; 6 flag = s2.getArea() - s1.getArea(); 7 return (int)(flag * 1000); 8 } 9 });
排序后输出即可。以下为类图与复杂程度:


题目集7(7-2)图形卡片分组游
戏,相比前一题多了分组操作,需要对创建的图形进行分组再组内排序输出。由于使用的还是类似的图形卡片,所以模块和上题相似,但是多了Trapezoid梯形,添加并继承Shape类即可。对于链表,因为要实现分组,所以我使用了多个链表,每种类型使用一个链表,只要在输入后判定其类型即可将其添加入相应的链表中。
//链表 ArrayList<Shape> list = new ArrayList<Shape>(); ArrayList<Shape> circle = new ArrayList<Shape>(); ArrayList<Shape> rectangle = new ArrayList<Shape>(); ArrayList<Shape> triangle = new ArrayList<Shape>(); ArrayList<Shape> trapezoid = new ArrayList<Shape>();
这里应该将相应类型的链表就做成相应类型,比如circle的链表就应该写成ArrayList<Circle> circle = new ArrayList<Circle>();这样才能保证创建的链表与其中元素的类型能够完全一致,不会出现错误添加的可能。
1 //记录已使用数据数量 2 int count = 0; 3 //创建图形 4 for(int i = 0; i < num.length - 1; i ++) { 5 Shape shape = null; 6 switch(num[i]) { 7 case 1: 8 shape = new Circle(num1[count]); 9 count += 1; 10 circle.add(shape); 11 break; 12 case 2: 13 shape = new Rectangle(num1[count], num1[count + 1]); 14 count += 2; 15 rectangle.add(shape); 16 break; 17 case 3: 18 shape = new Triangle(num1[count], num1[count + 1], num1[count + 2]); 19 count += 3; 20 triangle.add(shape); 21 break; 22 case 4: 23 shape = new Trapezoid(num1[count], num1[count + 1], num1[count + 2]); 24 count += 3; 25 trapezoid.add(shape); 26 break; 27 } 28 list.add(shape); 29 }
分组后就进行排序,对每一组的数据排序一次即可。以下为类图与复杂程度:


总体两次题目大框架相同,有类似的类设计,操作也只多了分组操作,但是第二题需要考虑的更多,比如该类型是否有元素等等,难度比第一题还是大了一些的。
题目集8和题目集9两道ATM机仿真题目的设计思路分析总结
题目集8ATM机类结构设计(一),主要完成ATM机的存取以及查询功能。这里根据题目要求,首先有一个银联的概念,ChinaUnionPay,银联中包含了几个银行Bank,每个银行有各自的ATM机器以及用户User,用户有属于自己的若干个银行账户Account,每个账户下又有隶属于该账户的若干张银行卡Card。根据题目要求,余额Balance存在账户中,每张卡共用余额。
在数据的连接方面,银联中拥有三个链表,分别存放银行、ATM以及用户,这三个链表即整个银联的公用数据,当用户在操作时只需要从中得到数据就可以操作了。银行类中包含了银行名称并链接了ATM机以及用户。ATM即银行私有的ATM机器,拥有序号,用户可以使用不同序号的ATM机进行存取查的操作。用户是银行的数据,其中包括用户的姓名、账户、余额以及卡的信息数据,在操作时需要比对或者更改数据等。账户中拥有card链表,包含了用户的所有卡片,每张卡片也有各自的密码,在存取时需要输入正确的密码才能进一步操作。当然每个类中都包含了各自属性的get、set方法,有关链表的都有相应的add操作方法。
这里放上Account类的代码供参考:
1 class Account{ 2 private String accountNum; 3 private double balance = 10000; 4 private ArrayList<Card> card = new ArrayList<Card>(); 5 6 Account(){ 7 8 } 9 10 Account(String accountNum){ 11 this.accountNum = accountNum; 12 } 13 //得到卡 14 public ArrayList<Card> getCard() { 15 return this.card; 16 } 17 //添加卡 18 public void addCard(Card card) { 19 this.card.add(card); 20 } 21 22 public String getAccountNum() { 23 return this.accountNum; 24 } 25 26 public void setBalance(double balance) { 27 this.balance = balance; 28 } 29 30 public double getBalance() { 31 return this.balance; 32 } 33 34 public String toString() { 35 return String.format("%.2f", balance); 36 } 37 38 }
整体类图如下:

ATM的设计主要包含其号码以及基本操作,包括存取款taking(Account account,double balance),当balance为正数时为取款操作,为负数时为存款操作;以及查询Query(Account account),返回用户的balance。
在设计中,拥有一个Handle类,这是在使用ATM机操作时调用的类,是主要的操作责任类。首先输入数据,还是我习惯的String类输入,之后切割,拆分,判定。判定好需要进行的操作后判定ATM是否存在,然后判定是否进行了跨行操作,之后是账户与银行卡是否存在,校验密码,再其次才是操作。省略切割拆分。
放上操作的部分:
1 //查询 2 if(strNum.length == 1) { 3 System.out.println("¥" + cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).toString()); 4 } 5 //存取 6 else { 7 int atmAdd = this.findAddOfATM(cup, strNum[2]); 8 int atmAddInBank = this.findAddOfATMInBank(cup, strNum[2]); 9 //atm存在校验 10 if(!this.validateOfATM(cup, strNum[2])) { 11 System.out.println("Sorry,the ATM's id is wrong."); 12 System.exit(0); 13 } 14 //校验跨行操作 15 if(!this.validateOfBank(cup, strNum[2], cardAdd)) { 16 System.out.println("Sorry,cross-bank withdrawal is not supported."); 17 System.exit(0); 18 } 19 //密码校验 20 if(!cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).getCard().get(cardAdd[3]).validateOfKey(strNum[1])) { 21 System.out.println("Sorry,your password is wrong."); 22 System.exit(0); 23 } 24 if(Double.parseDouble(strNum[3]) > 0) 25 if(!this.validateOfTaking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), Double.parseDouble(strNum[3]))) { 26 System.out.println("Sorry,your account balance is insufficient."); 27 System.exit(0); 28 } 29 cup.getBank().get(cardAdd[0]).getATM().get(atmAdd).taking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), Double.parseDouble(strNum[3])); 30 if(Double.parseDouble(strNum[3]) > 0) 31 System.out.println(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getName() + "在" + cup.getBank().get(cardAdd[0]).getBankName() + "的" + cup.getBank().get(cardAdd[0]).getATM().get(atmAddInBank).getATMNum() + "号ATM机上取款¥" + strNum[3]); 32 else 33 System.out.println(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getName() + "在" + cup.getBank().get(cardAdd[0]).getBankName() + "的" + cup.getBank().get(cardAdd[0]).getATM().get(atmAddInBank).getATMNum() + "号ATM机上存款¥" + strNum[3].replace("-", "")); 34 System.out.println("当前余额为¥" + cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).toString()); 35 }
这里设计了很多个方法来检验,比如密码校验、atm存在校验、账号存在校验等,这里放上一个供参考:
1 //atm存在校验 2 public boolean validateOfATM(ChinaUnionPay cup, String atmNum) { 3 for(int i = 0; i < cup.getBank().size(); i ++) { 4 for(int j = 0; j < cup.getBank().get(i).getATM().size(); j ++) { 5 if(cup.getBank().get(i).getATM().get(j).getATMNum().equals(atmNum)) 6 return true; 7 } 8 } 9 return false; 10 }
这里还有一个重要的内容,使用了数组来保存搜索到的用户数据的所在位置,因为数据保存在银联中,在操作和存取是需要取出数据。但是,根本不需要这么麻烦,只需要再创建一个用户数据进行赋值就够了,这里没有充分发挥类与类间的关系,也没有合理的使用多态,造成了代码的冗长复杂,并且复用率低,需要加强改进。
银联中数据的来源要通过数据初始化实现,这里需要自动初始化,即代码中实现。再Main类中需要创建银联类,之后在银联中添加需要使用的银行、用户、账号、卡、ATM机,达到数据的初始化目的:
1 public class Main { 2 3 public static void main(String[] args) { 4 // TODO Auto-generated method stub 5 //数据初始化 6 ChinaUnionPay cup = new ChinaUnionPay(); 7 cup.addBank("中国工商银行"); 8 cup.addBank("中国建设银行"); 9 10 ATM atm = new ATM("05"); 11 cup.getBank().get(0).addATM(atm); 12 atm = new ATM("06"); 13 cup.getBank().get(0).addATM(atm); 14 atm = new ATM("01"); 15 cup.getBank().get(1).addATM(atm); 16 atm = new ATM("02"); 17 cup.getBank().get(1).addATM(atm); 18 atm = new ATM("03"); 19 cup.getBank().get(1).addATM(atm); 20 atm = new ATM("04"); 21 cup.getBank().get(1).addATM(atm); 22 23 User user = new User("张无忌"); 24 cup.getBank().get(0).addUser(user); 25 user = new User("韦小宝"); 26 cup.getBank().get(0).addUser(user); 27 user = new User("杨过"); 28 cup.getBank().get(1).addUser(user); 29 user = new User("郭靖"); 30 cup.getBank().get(1).addUser(user); 31 32 Account account = new Account("3222081502001312389"); 33 cup.getBank().get(0).getUser().get(0).addAccount(account); 34 account = new Account("3222081502001312390"); 35 cup.getBank().get(0).getUser().get(0).addAccount(account); 36 account = new Account("3222081502001312399"); 37 cup.getBank().get(0).getUser().get(0).addAccount(account); 38 account = new Account("3222081502051320785"); 39 cup.getBank().get(0).getUser().get(1).addAccount(account); 40 account = new Account("3222081502051320786"); 41 cup.getBank().get(0).getUser().get(1).addAccount(account); 42 account = new Account("3217000010041315709"); 43 cup.getBank().get(1).getUser().get(0).addAccount(account); 44 account = new Account("3217000010041315715"); 45 cup.getBank().get(1).getUser().get(0).addAccount(account); 46 account = new Account("3217000010051320007"); 47 cup.getBank().get(1).getUser().get(1).addAccount(account); 48 49 Card card = new Card("6222081502001312389"); 50 cup.getBank().get(0).getUser().get(0).getAccount().get(0).addCard(card); 51 card = new Card("6222081502001312390"); 52 cup.getBank().get(0).getUser().get(0).getAccount().get(1).addCard(card); 53 card = new Card("6222081502001312399"); 54 cup.getBank().get(0).getUser().get(0).getAccount().get(2).addCard(card); 55 card = new Card("6222081502001312400"); 56 cup.getBank().get(0).getUser().get(0).getAccount().get(2).addCard(card); 57 card = new Card("6222081502051320785"); 58 cup.getBank().get(0).getUser().get(1).getAccount().get(0).addCard(card); 59 card = new Card("6222081502051320786"); 60 cup.getBank().get(0).getUser().get(1).getAccount().get(1).addCard(card); 61 card = new Card("6217000010041315709"); 62 cup.getBank().get(1).getUser().get(0).getAccount().get(0).addCard(card); 63 card = new Card("6217000010041315715"); 64 cup.getBank().get(1).getUser().get(0).getAccount().get(0).addCard(card); 65 card = new Card("6217000010041315718"); 66 cup.getBank().get(1).getUser().get(0).getAccount().get(1).addCard(card); 67 card = new Card("6217000010051320007"); 68 cup.getBank().get(1).getUser().get(1).getAccount().get(0).addCard(card); 69 70 Handle handle = new Handle(cup); 71 handle.handle(); 72 } 73 74 }
以下为整体复杂程度图:

明显造成了过于复杂的问题。
题目集9ATM机类结构设计(二),相比第一题要实现跨行存取以及额外添加贷记卡的概念,贷记卡可以实现透支消费,即拥有额外50000元的透支消费额度,若透支消费需收取相应手续费。跨行取款也需要收取相应的手续费。
要实现这个功能,只需要在Handle中进行修改即可。首先需要添加判定,第一个是将跨行操作的判定修改为继续操作,但是存取时需要收取额外费用,这里需要有记号进行标记。之后是是否为贷记卡的透支消费,如果透支,则允许卡中余额为负数,并且收取透支造成的额外费用,也需要额外的标记。
在设计判定方法时也需要做出修改,比如余额的校验就得先判定对方是否为贷记卡,这需要额外的判定。
还有需要注意的是扣款的顺序以及余额判定的问题,到底是先进行扣款还是先进行判定需要反复实验。比如当一张贷记卡里拥有10000的额度,但是当跨行取出10000元时要进行扣款,跨行手续费扣除后就变成了透支消费,这又要额外扣除透支手续费。所以这其中还有很多问题,我并没有在规定任务提交时间内全部解决,实在无能为力。
以下是我的结果:
1 public void handle() { 2 Scanner input = new Scanner(System.in); 3 String str = input.nextLine(); 4 5 while(!str.equals("#")) { 6 String[] strNum = str.split("[ ]+"); 7 8 //卡号存在校验 9 if(!this.validateOfCard(cup, strNum[0])) { 10 System.out.println("Sorry,this card does not exist."); 11 System.exit(0); 12 } 13 14 //位置信息,方便书写 15 int[] cardAdd = new int[4]; 16 for(int i =0; i < 4; i ++) { 17 cardAdd[i] = this.findAddOfCard(cup, strNum[0], i + 1); 18 } 19 20 //手续费 21 double premium; 22 //查询 23 if(strNum.length == 1) { 24 System.out.println("业务:查询余额 " + "¥" + cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).toString()); 25 } 26 27 //存取 28 else { 29 int atmAdd = this.findAddOfATM(cup, strNum[2]); 30 int atmAddInBank = this.findAddOfATMInBank(cup, strNum[2]); 31 32 //atm存在校验 33 if(!this.validateOfATM(cup, strNum[2])) { 34 System.out.println("Sorry,the ATM's id is wrong."); 35 System.exit(0); 36 } 37 38 //校验跨行操作 39 /*if(!this.validateOfBank(cup, strNum[2], cardAdd)) { 40 System.out.println("Sorry,cross-bank withdrawal is not supported."); 41 System.exit(0); 42 }*/ 43 44 //密码校验 45 if(!cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).getCard().get(cardAdd[3]).validateOfKey(strNum[1])) { 46 System.out.println("Sorry,your password is wrong."); 47 System.exit(0); 48 } 49 50 //取款 51 if(Double.parseDouble(strNum[3]) > 0) { 52 53 //校验余额 54 if(!this.validateOfTaking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), Double.parseDouble(strNum[3]))) { 55 System.out.println("Sorry,your account balance is insufficient."); 56 System.exit(0); 57 } 58 59 //检验贷记卡 60 if(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).getType().equals("贷记")) { 61 62 //检验是否透支 63 if(this.validateOfOverdraft(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), Double.parseDouble(strNum[3]))) { 64 if(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).getBalance() > 0) 65 premium = cup.getPremium() * (Double.parseDouble(strNum[3]) - cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).getBalance()); 66 else 67 premium = cup.getPremium() * Double.parseDouble(strNum[3]); 68 cup.getBank().get(cardAdd[0]).getATM().get(atmAdd).taking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), premium); 69 } 70 } 71 72 //校验是否跨行 73 if(!this.validateOfBank(cup, strNum[2], cardAdd)) { 74 premium = cup.getBank().get(atmAdd).getPremium() * Double.parseDouble(strNum[3]); 75 76 //扣取跨行手续费 77 cup.getBank().get(cardAdd[0]).getATM().get(atmAdd).taking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), premium); 78 } 79 80 //取钱主体 81 cup.getBank().get(cardAdd[0]).getATM().get(atmAdd).taking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), Double.parseDouble(strNum[3])); 82 } 83 84 //存款 85 else 86 cup.getBank().get(cardAdd[0]).getATM().get(atmAdd).taking(cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]), Double.parseDouble(strNum[3])); 87 88 //输出 89 if(Double.parseDouble(strNum[3]) > 0) 90 System.out.println("业务:取款 " + cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getName() + "在" + cup.getBank().get(atmAdd).getBankName() + "的" + cup.getBank().get(atmAdd).getATM().get(atmAddInBank).getATMNum() + "号ATM机上取款¥" + String.format("%.2f", Double.parseDouble(strNum[3]))); 91 else 92 System.out.println("业务:存款 " + cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getName() + "在" + cup.getBank().get(atmAdd).getBankName() + "的" + cup.getBank().get(atmAdd).getATM().get(atmAddInBank).getATMNum() + "号ATM机上存款¥" + String.format("%.2f", Double.parseDouble(strNum[3].replace("-", "")))); 93 System.out.println("当前余额为¥" + cup.getBank().get(cardAdd[0]).getUser().get(cardAdd[1]).getAccount().get(cardAdd[2]).toString()); 94 } 95 str = input.nextLine(); 96 } 97 }
类图于前一题基本相同,复杂度也相似,就不做赘述。
总结
总体而言这几次题目集算是让我成长了很多,终于搞明白了类与类之间关系设计的重要性。就前两题而言还是比较合理的,用继承就能够将所有图形统一起来管理,是比较合理的设计。后两题中其实一开始并不是这么设计的,用的是聚合,导致了数据很难同步的问题,数据无法同步就无法操作,问题非常严重,导致了大面积的重写。这里也让我明白,程序、系统不是上手直接写就能做出来的,需要先进行合理的概念设计以及关系梳理,做ATM模拟就需要先想ATM是如何操作的,如何做到存取的,他是依靠什么来得到数据的等等。类与类间的关系还需要进一步的学习!
经过一段时间学生与课堂的磨合,现在感觉已经是非常好的生态,我们学生学起来也不废劲,老师讲起来也更清楚,更轻松,总体上非常不错,希望我们可以继续一起进步!
浙公网安备 33010602011771号