Blog作业总结03
前言:
第三阶段的oop作业相比之前,更加贴合面向对象,实现少部分代码通过增、删、改、查而大体代码结构、框架甚至内容都不改变,却能完成用户所需的功能。同时,本次题目的关联性较高,都是同一种逻辑走向,并在一定基础上实现更多的功能。
题目集七:
题量:较少
难度、所含知识点:和前面的几次作业对比,这三次的作业都是紧贴面向对象进行设计的。题目集七中第一题 图形卡片排序游戏 根据接口Comparable,同时将数据存入ArrayList中,最后将面积从大到小进行排序并在此基础上改变list中的对应图形的对应面积顺序。总的来说,本题类中方法的搭建,以及关于list内部图形和面积的排序是本题的难点所在,解决完这两个问题,其他问题也就迎刃而解了。
第二题图形卡片分组游戏在上一题的基础上加入了新的ArrayList。这里将不同的图形分类放入同一个组,同时在该组中进行从大到小的分类。即思路为:参数全部放入Shape的动态数组中,在根据图形进行分组存入新的动态数组中对应图形的动态数组中。大致思路构建完问题便解决了。
题目集八:
题量:中等
难度、所含知识点:本题ATM机类结构设计(一)多处运用ArrayList将所需数据按类分别存储在List中,同时十分贴合面向对象,结合了实际生活中的应用。第一次接触时,面对给出的功能在没有类图的情况下需要学生自己构建所需类图以用于代码的框架书写,难度略大。同时,这是第一次接触多个不同的类相互关联,关系错综复杂,调用数据时需要小心谨慎为好。
题目集九:
题量:中等
难度、所含知识点:此题ATM机类结构设计(二)还是和上一题大体一致,只是增加了跨行和透支的余额的功能。同时,本次题目思路不变,但代码书写换了一种写法,大体不影响。此代码将所有数据封装,调用数据时会更加方便。同时在处理输入数据时,采用了split()方法和简单的正则进行数据的切割和处理。
7-7-1:图形卡片排序游戏
1、设计与分析
(1)设计:
实验要求:
- 首先,在一行上输入一串数字(1~4,整数),其中,1代表圆形卡片,2代表矩形卡片,3代表三角形卡片,4代表梯形卡片。各数字之间以一个或多个空格分隔,以“0”结束。例如:
1 3 4 2 1 3 4 2 1 3 0 - 然后根据第一行数字所代表的卡片图形类型,依次输入各图形的相关参数,例如:圆形卡片需要输入圆的半径,矩形卡片需要输入矩形的宽和长,三角形卡片需要输入三角形的三条边长,梯形需要输入梯形的上底、下底以及高。各数据之间用一个或多个空格分隔。
输出内容如下:
- 排序前的各图形类型及面积,格式为
图形名称1:面积值1图形名称2:面积值2 …图形名称n:面积值n,注意,各图形输出之间用空格分开,且输出最后存在一个用于分隔的空格; - 排序后的各图形类型及面积,格式同排序前的输出;
- 所有图形的面积总和,格式为
Sum of area:总面积值。
主类中用于处理选择图形样式的数据,后根据类图构建好大体框架后开始增加方法。这里在DealCardList类中构建ArrayList<Card> cardList = new ArrayList<Card>();进行图形的选择以及输入图形的参数,同时存入动态数组用于后续的排序。
ArrayList<Card> cardList = new ArrayList<Card>(); public DealCardList() { super(); } public DealCardList(ArrayList<Integer> list) { for(int i=0;i<list.size();i++) { switch(list.get(i)) { case 1:{ Card card=new Card(new Circle(Main.input.nextDouble())); card.getShape().setShapeName("Circle:"); cardList.add(card); break; } case 2:{ Card card=new Card(new Rectangle(Main.input.nextDouble(),Main.input.nextDouble())); card.getShape().setShapeName("Rectangle:"); cardList.add(card); break; } case 3:{ Card card=new Card(new Triangle(Main.input.nextDouble(),Main.input.nextDouble(),Main.input.nextDouble())); card.getShape().setShapeName("Triangle:"); cardList.add(card); break; } case 4:{ Card card=new Card(new Trapezoid(Main.input.nextDouble(),Main.input.nextDouble(),Main.input.nextDouble())); card.getShape().setShapeName("Trapezoid:"); cardList.add(card); break; } default:{ System.out.println("Wrong Format"); System.exit(0); break; } } } }
接着在抽象类Shape中加入抽象方法便于后续的方法调用
public abstract double getArea() ; public abstract boolean validate() ; public abstract String toString() ;
同时,三个图形类都继承Shape父类并复写以上三种方法进行数据的计算与格式化
(三个图形的数据处理这里可采用6-7-5图形继承与多态中的思想)
<1> Circle类:return radius*radius*Math.PI
<2> Rectangle类:return length*width
<3> Triangle类:return Math.sqrt(num*(num-side1)*(num-side2)*(num-side3))
接着用allarea=allarea+cardList.get(i).getShape().getArea();分别赋值求面积之和
<4> 接口中的方法复写:
@Override public int comparaTo(Card card) { if(shape.getArea() > card.getShape().getArea()) { return 1; } else { return -1; } }
<5> 排序:此处的由于是用动态数组来存储计算的各图形面积的,除了单纯排序,还得注意调整面积对应的图形顺序是否一致。如果列表中的前一个小于后一个,则这里将cardList.get(i+1)放入List中i对应的位置,cardList.get(i)放在i+1对应的位置上,实现顺序的调换,这是第一次接触如此方法,具体代码如下:
public void cardSort() { Card im = null; boolean sort = true; int i = 0; while(sort) { sort = false; for( i = 0;i < cardList.size()-1;i++) { if(cardList.get(i).comparaTo(cardList.get(i+1)) < 0) { im = cardList.get(i); cardList.set(i, cardList.get(i+1)); cardList.set(i+1,im); im = null; sort = true; } } } }
(2)分析:
根据PowerDesigner的类图显示,三个图形类Card、Rectangle、Triangle全都继承了Shape类,同时,Card类继承接口Comparable。

使用SourceMonitor对该函数的复杂度进行检测,结果如下:
SourceMonitor的生成报表:

由图可知,该题复杂度较低,主要是因为其中的if-else语句较少故此复杂度较小。
2、踩坑心得
(1)计算凡是有涉及圆周率的均用Math.PI才可过测试点而不能用3.14进行计算。
(2)这里在传入不同图形的各个参数时,代码的书写修改了很多次,但最后还是先将个参数放入图形类中new Triangle(Main.input.nextDouble(),Main.input.nextDouble(),Main.input.nextDouble())
再将三个类分别放入Card中,这样的书写格式会为接下来的代码设计变得更加迅速。
(3)在排序的时候,只是单纯的将位置调换,而没有考虑将Card设为空;导致传参时会出现一些问题。
3、改进建议:
虽然前面分析的排序交换过程思路较为清晰,但思维的实现较为复杂,借用List中的Collections.swap方法会使代码更加简洁,具体代码如下:
boolean sort = true; int i = 0; while(sort) { sort = false; for( i = 0;i < cardList.size()-1;i++) { if(cardList.get(i).comparaTo(cardList.get(i+1)) < 0) { Collections.swap(cardList, i, i+1); sort = true; } } }
7-7-2:图形卡片分组游戏
1、设计与分析
(1)设计:
实验要求:
- 在一行上输入一串数字(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:面积值。
首先在DealCardList中创建五个新的动态数组,后续用来储存四种图形的参数。同时,根据输出的The original list:可知,在选择是什么图形之后,所有的图形数据都要先全部放入一个动
态数组中(这里即cardList),再分别将该对应的图形放入一个图形的动态数组中,例如圆的:
case 1:{ Card card=new Card(new Circle(Main.input.nextDouble())); card.getShape().setShapeName("Circle:"); cardList.add(card); cirList.add(card); break; }
这样才能保证实现The Separated List:时,同一图形的数据储存在一个ArrayList中,例如这种形式:
[Circle:19.63 Circle:21.24 Circle:226.98 Circle:105.68 Circle:14.25 Circle:120.81 Circle:1092.72 ][Rectangle:8.05 Rectangle:9.45 Rectangle:16.00 ][Triangle:4.65 Triangle:29.80 ][Trapezoid:50.49 Trapezoid:175.56 ]
最后,排序采用和上一道同样的思想,这里就不再重复了。
(2)分析:
根据PowerDesigner的类图显示,四种图形类Card、Rectangle、Triangle、Trapezoid、全都继承Shape类,同时,Card类继承接口Comparable。

使用SourceMonitor对该函数的复杂度进行检测,结果如下:
SourceMonitor的生成报表:

由图可知,本题复杂度较低,主要是if语句使用较少的缘故。
2、踩坑心得:
一开始读完题目后,大致构建出了需要两个数组进行储存的形式,但是不知道如何实现这种思想,一开始是想在ArrayList泛型中再放入一个ArrayList,但这种不符合规范,于是想到,为何不直接再写四个动态数组直接放入数据进行排序处理呢。
其他由于和第一题思路几乎一模一样,处理完上述问题后,代码书写也十分顺畅了。
3、改进建议:
这里也是和上面一样,如果了解过了List中的Collections.swap方法,则可省去排序方法中的繁琐代码了。
8-7-1: ATM机类结构设计(一)
1、设计与分析
(1)设计:
实验要求:
设计ATM仿真系统,每一行输入一次业务操作,可以输入多行,最终以字符#终止。具体每种业务操作输入格式如下:
- 存款、取款功能输入数据格式: 卡号 密码 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编号]上存款¥[金额]
当前余额为¥[金额]
其中,[]说明括起来的部分为输出属性或变量,金额均保留两位小数。
④查询余额业务输出
¥[金额]
金额保留两位小数。
以下是需要初始化的数据:


由题目所要求的数据类型以及输入数据可知,此次题目中的类需要互相继承,并且设为相互继承(形成你中有我,我中有你的形式)会使后续数据的调用更加简便。,
同时,由输入数据可知,本题是通过卡号进行其他数据的寻找,所以代码的第一部分便是数据的搭建(数据初始化),将“用户姓名”、“银行名称”、“银行账号”、“初始余额”、“隶属卡号”以及“ATM机的编号”分别传入不同的ArrayList中进行存储。
接下来便是类的构造(如下图所示):





在构造时,在Bank中存入银行名称,在User中传入用户姓名,在ATM中放入编号,在BankAccount中放入账户余额,在Card中放入卡号,银行账号以及密码进行数据的初始化。
这里值得注意的是,我将银联放在了全局变量里面,便于下面数据的调用。
private static final String tbank = null; static BankUnion bn = new BankUnion(); static ArrayList<String>list=new ArrayList<String>();
接着便是其他变量的传入了,由于传入的思想大体一致,这里就举Card和BankAccount以及User和BankAccount两个例子:
① 创建完账户后,就往银行卡里存入卡号账户密码。
BankAccount ba1 = new BankAccount("3217000010041315709",10000.00); ba1.getCards().add(new Card("6217000010041315709",ba1,"88888888")); ba1.getCards().add(new Card("6217000010041315715",ba1,"88888888"));
② 创建完User后将名字传入其中,同时,在用户的账户中存入银行账号。
User yg = new User("杨过"); yg.getBankaccount().add(ba1); yg.getBankaccount().add(ba2); for(int i = 0;i < yg.getBankaccount().size();i++) { yg.getBankaccount().get(i).setOwneruser(yg); }
在完成所用数据初始化后,开始用正则分割并处理数据。
<1>只执行查询余额:
这里我用去除该字符串当中的首尾空白字符后计算字符串长度,若刚好为19,则直接用输入的卡号寻找此时账户中的余额并打印。
<2>执行其他操作:
这里我利用replaceAll("[ ]+"," "),用一个空格取代所有出现的空格,同时split(" ")识别到空格就分割,并分别存入ArrayList字符串数组中。其中,list.get(0)、list.get(1)、list.get(2)、list.get(3)分别表示“卡号”、“密码”、“ATM编号”以及“暂时的余额” ,同时进行赋值。
double money1=0; double nowbalance = 0; String nowcardno = list.get(0); String username = null; String bankname = null; String ATMnumber = list.get(2);
<3>校验错误信息
这里我写了四个boolean形式的方法用于校验正误
是否跨行:
atmid.equals(bankt.getAtms().get(i).getAtmid());
卡号是否存在:
cardno.equals( bn.getBanks().get(a).getAccounts().get(b).getCards().get(c).getCardNo())
ATM编号是否存在:
atmid.equals(bn.getBanks().get(a).getAtms().get(b).getAtmid())
密码是否匹配:
password.equals("88888888")
<4>通过卡号获取所要信息:
由于所有的信息都是通过银行进行获取,故此先统一根据卡号获取相关信息。
Bank tbank = null; for(int i = 0;i < bn.getBanks().size();i++) { for(int j = 0;j< bn.getBanks().get(i).getAccounts().size();j++) { for(int k = 0;k < bn.getBanks().get(i).getAccounts().get(j).getCards().size();k++) { if(bn.getBanks().get(i).getAccounts().get(j).getCards().get(k).getCardNo().equals(nowcardno)) { nowbalance = bn.getBanks().get(i).getAccounts().get(j).getBalance(); username = bn.getBanks().get(i).getAccounts().get(j).getOwneruser().getUsername(); bankname = bn.getBanks().get(i).getBankname(); tbank = bn.getBanks().get(i); } } } }
<5> 这里有一个隐藏的测试点,便是,当中间有输入错误时,报错之后,下面的数据仍能继续进行处理,故每次处理完后,我将list中的数据进行清除,同时用continue;进行下面的正常操作。
if(!showStatus(list.get(0), list.get(1), list.get(2), Double.valueOf(list.get(3)), money1,tbank,nowbalance)) { str=input.nextLine(); list.clear(); continue; }
(2)分析:
根据PowerDesigner的类图显示,多个类之间存在
关联关系彼此之间关系连接密切。

使用SourceMonitor对该函数的复杂度进行检测,结果如下:
SourceMonitor的生成报表:

由图可知,该复杂度几乎为0。
2、踩坑心得
(1)在构造时,只是将名字放入List链表中,而并没有将名字存入用户的setOwneruser方法中导致提取姓名时本应用卡号提取姓名,却无法调用getOwneruser方法。
(2)在处理输入数据时,替换空格用replaceAll而不是replace,All可替换一个及以上的所有空格。
(3)在用卡号寻找其他信息的时候,最后一步的if判准都是bn.getBanks().get(i).getAccounts().get(j).getCards().get(k).getCardNo().equals(nowcardno),
否则无法得到正确信息且逻辑就存在问题了。
(4)值得注意的是,每个类中的需要表示题目所给数据的对象,凡是String型的都先设为null便于后续参数的传入。同时,对象中使用ArrayList,标准格式应为private ArrayList<Card> cards = new ArrayList<Card>();而不是private ArrayList<Card> cards
3、改进建议:
此题整体看下来,虽然功能得以实现且已按照要求进行数据的处理与获取,但是书写格式较为混乱。故此,在这应重新添加两个类,一个用于校验数据,另一个用来进行数据的计算与获取,这样会让调理更加清晰。
9-7-1: ATM机类结构设计(二)
1、设计与分析
(1)设计:
实验要求:设计ATM仿真系统,分别实现
- 取款功能输入数据格式: 卡号 密码 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编号]上取款¥[金额]
当前余额为¥[金额]
其中,[]说明括起来的部分为输出属性或变量,金额均保留两位小数。
③查询余额业务输出
业务:查询余额 ¥[金额]
金额保留两位小数。
以下是需要初始化的数据:
和前一次的数据对比可知,这次多加了隶属银行“中国农业银行”以及账号种类,银行卡包含借记卡和信用卡两类,其中,借记卡无法透支,也无需考虑透支取款手续费及透支最大限额的问题;而贷记卡则需考虑。且此次ATM允许跨行办理相关业务,值得注意的是,跨行需扣除一定的行取款手续费。
Bank类:
这里我采用新建一个Bank对象“中国农业银行”
Bank ccb = new Bank("中国建设银行"); Bank icbc = new Bank("中国工商银行"); Bank aboc= new Bank("中国农业银行");
Account类:
这里我将账号种类放入了Account类中
public Account(String accountNO, String accountKind, double balance, User user, Bank bank) { super(); this.accountNO = accountNO; this.accountKind = accountKind; this.balance = balance; this.user = user; this.bank = bank; }

其余数据的初始化都可按照上一ATM的设计思路进行初始化。
业务类用于数据处理
Withdraw类
<1>跨行取款手续费:
这里我在Bank类中设计了rate()方法用来存放手续费率。初始值为0。银行账户中的通过卡号来提取的所属银行与用户在ATM机上对应的银行不一致时,则开始进行跨行操作,,数据设置如下:
/** * 跨行时手续费的计算 */ aTM.getBank().setRate(0); if(!account.getBank().getBankName().equals(aTM.getBank().getBankName())) { if(aTM.getBank().getBankName().equals("中国建设银行")){ aTM.getBank().setRate(0.02); } if(aTM.getBank().getBankName().equals("中国工商银行")){ aTM.getBank().setRate(0.03); } if(aTM.getBank().getBankName().equals("中国农业银行")){ aTM.getBank().setRate(0.04); } }
<2>是否可贷款:
经过数据的对比计算可知,当不进行贷款操作时,账户所要扣除的金额为amount+aTM.getBank().getRate()*amount;当使用贷记账号且需要进行贷款时,若账户余额为正,则用(amount-balance)*0.05;否则为amount*0.05;
double realamount = amount + aTM.getBank().getRate()*amount; double ra2 = amount + aTM.getBank().getRate()*amount + (amount -balance)*0.05; if(balance < 0){ ra2 = amount + aTM.getBank().getRate()*amount + amount*0.05; }else{ ra2 = amount + aTM.getBank().getRate()*amount + (amount - balance)*0.05; }
根据题目要求可知,若取款金额(包括跨行取款)大于余额,realamount > balance,打印错误信息;若用贷记账号,则若透支金额超过规定最大透支金额,balance-ra2<-50000,打印错误信息。
<3>结算数据:
if(amount >= 0){//取款 if(realamount <= balance){/*account.getAccountKind().equals(" 借记账号 " )&&*/ account.setBalance(balance - realamount);//取款更新余额操作 }else if(account.getAccountKind().equals(" 贷记账号 ") && amount>balance && balance>0){ /*&&amount*0.05<=50000&&amount>balance*/ account.setBalance(balance - ra2);//取款更新余额操作 }else if(account.getAccountKind().equals(" 贷记账号 ")&&balance<=0){ account.setBalance(balance - amount - aTM.getBank().getRate() * amount - amount * 0.05);//取款更新余额操作 }else{ System.out.println("Sorry,your account balance is insufficient."); System.exit(0); } }else{//存款 account.setBalance(account.getBalance() - amount); }
(2)分析:
根据PowerDesigner的类图显示,仍是多个类相互关联,联系十分紧密。

使用SourceMonitor对该函数的复杂度进行检测,结果如下:
SourceMonitor的生成报表:
由图可知,该题的复杂度较大,主要是业务Withdraw类中的if语句较多导致复杂度提高。
2、踩坑心得
(1)首先是手续费的问题,开始我将aTM.getBank().setRate(0.02);写成了account.getBank().setRate(0.02);数据产生一定问题;同时根据逻辑可知,这里是通过ATM的编号获取对应的银行从而对比是否跨行故可知是用前者进行数据的传输。
(2)凡是遇到判断条件和计算,都是优先计算再进行定制的
(2)其次,由于我复制时,人名多复制了一个空格导致最后所有数据调配好时,PTA直接四个格式错误,最后才发现,多的空格恰好和人名一起作为字符串传出来,导致最终的四个格式错误。
3、改进建议:
在进行数据处理的时候,若想让代码变得更加清楚,可单独创建一个类专门用来存储数据和计算数据。以免Withdraw中的方法、对象过于拥挤。
作业总结:
本阶段的作业谈的不再是难点,而是更加贴合实际的功能实现。这一阶段的题目所涉及的知识点则是前面学习的多种知识点进行串联而成,故此次的代码自己将类等结构的构造规划好之后,代码的书写也变得通畅许多。
其次,ATM的题目让我熟悉掌握了ArrayList泛型为类的使用方法,同时也学会了多个类一起相互关联的用处所在。
2021-06-11

浙公网安备 33010602011771号