第三次oo博客

一、前言

  第七次题目集,题目7-1是一个卡片游戏,根据输入形状的类型给定相应的几何数据,计算并根据大小排序后输出各个面积及总和,主要考察Comparable 接口中的 CompareTo()方法,抽象类,继承关系和ArrayList的用法,以及开闭原则,题量一般,由于给出比较完整的类图所以难度也一般。7-2基于7-1的基础上增加了对图形对象分类且排序的功能,考察内容相似,题量一般,由于是在7-1的基础上进行迭代所以难度也较低。

  第八次题目集,题目7-1是实现一个类似银行工作流程和业务逻辑的程序,考察了代码结构设计的开闭原则,对输入进行处理的操作等,题量略大,难度总体比较大,因为没有给出具体类图所以需要自己设计整体结构,其次还要理清各个类间关系。

  第九次题目集,题目7-1是在题目集八7-1以及老师所给源码的基础上进行功能的扩充,增加了贷记卡以及进行跨行和透支操作时产生相应的手续费,考察了多态以及开闭原则的设计以及进行相应操作时的业务逻辑。题量也较大,总体难度略有下降,因为只是在原有的部分源码基础上进行相应功能的扩充。

二、设计与分析(重难点题目)

①题目集7(7-1),(7-2)

  7-1:

  类图:

  

 

 

  复杂度:
  

  

  从类图分析主要是各图形类对抽象的Shape的继承,以及Card类使用Comparable 接口中的 CompareTo()方法时的继承,通过DealCardList类进行相应的处理。通过分析复杂度,发现最大复杂度为5,分析代码观察到复杂度主要是用在遍历图形的ArrayList以及排序算法。

  例如:

  

    public boolean validate() {
      for(int i = 0; i < cardList.size(); i++) {
          if(!cardList.get(i).getShape().validate()) {
              return false;
          }
      }
      return true;
    }
    public void cardSort() {
      for(int i = 0; i < cardList.size() - 1; i++) {
          for(int j = 0; j < cardList.size() - i -1; j++) {
            if(cardList.get(j).compareTo(cardList.get(j+1)) < 0 ) {
                    Collections.swap(cardList,j,j+1);
            }
        }
      }
  }

 

  7-2:

  类图:

  

  复杂度:

  

 

  从类图中可以观察出整体代码较前次题目并没有进行很大程度的改变,只是在DealCardList类中加入了一些新方法和动态链表用来实现相应分类等功能。复杂度也与之前相同,同样是用于对链表的遍历和排序算法。

 

  题集小结:

  两次作业需求相似代码逻辑基本相同,整体解题思路是按照题目中所给出的类图逐步写出相关的类,三角形、矩形、圆形等图形类都是继承于抽象类Shape,而Shape类中包含简单的构造方法以及获取面积和判断数据合法性的抽象方法,通过子类的继承及重载实现相应功能然后在Card类中通过Comparable接口的CompareTo()方法,实现比较两个图形面积大小的功能为后面排序算法做铺垫。最后在DealCardList类中先对原有数据链表根据图形类型录入相应几何参数,进而创建相应图形对象,添加到泛型为<Card>类型的动态链表中,进而通过各方法遍历链表实现排序,求和,判断合法性等一系列操作。

  具体呈现一些比较重要的类中方法:

  此次作业均使用了Comparable接口,所以在Card类中实现了对CompareTo()方法的重写: 

    @Override
  public int compareTo(Card o) {
    return (int)((shape.getArea()-o.getShape().getArea()) * 10000);
  }

 

  注意:这里在方法中的乘10000(精度可能出现问题)是考虑到可能在转换成整形时会将小数位舍掉而对结果产生影响。此方法的功能就是简单的比较两个数的大小。

  基于上一个方法也就可以在DearCardList类实现排序的功能,此次均采用冒泡排序法:

  

  public void cardSort() {
    for(int i = 0; i < cardList.size() - 1; i++) {
      for(int j = 0; j < cardList.size() - i -1; j++) {
        if(cardList.get(j).compareTo(cardList.get(j+1)) < 0 ) {
          Collections.swap(cardList,j,j+1);
        }
      }
    }
  }

 

  

②题目集8(7-1)、题目集9(7-1)两次题目同样为迭代式。

  8-7-1:

  类图:

  

    

  复杂度:
  

  

  从类图分析,此次类间关系多采用组合的方式如各银行和ATM类之间,以及用户和账户之间等,导致耦合度比较高,没有完整实现单一原则。此外也将银联抽象化,设置了存取款的抽象方法,通过子银行继承实现。思路比较清晰但是整体结构有待提高。从复杂度来看,最大复杂度为11,主要用于最初对输入数据的正则处理以及后判断数据合法性时遍历多重链表。

  解题思路还是比较清晰,首先分析出银行附属于银联,用户附属于银行,账户附属于用户等一系列附属关系,创建相应的动态链表实现多用户及用户多个账户,账户多个附属卡的逻辑。然后通过分析所给示例分析出各卡内金额源于其源账户,即卡内没有金额以及通过银行对账户余额进行更改,由ATM判断操作合法性等业务逻辑,从而实现各实体类。

  列举其中一些重要方法:

  在ATM类中,首先判断了卡号的合法性,需要遍历多重链表,因此为了简便后续操作,这里记录了卡所在各层次的位置:

  public boolean checkCardNo() {
        //1代表中国建设银行
        //2代表中国工商银行
        for(int i = 0; i < icbc.list.size(); i++) {
            for(int j = 0; j < icbc.list.get(i).list.size(); j++) {
                for(int k = 0; k < icbc.list.get(i).list.get(j).list.size(); k++) {
                    if(cardNo.equals(icbc.list.get(i).list.get(j).list.get(k).getCardNo())) {
                        num[0] = 2;    
                        num[1] = i;
                        num[2] = j;
                        num[3] = k;
                        return true;
                    }
                }
            }
        }
        
        for(int i = 0; i < ccb.list.size(); i++) {
            for(int j = 0; j < ccb.list.get(i).list.size(); j++) {
                for(int k = 0; k < ccb.list.get(i).list.get(j).list.size(); k++) {
                    if(cardNo.equals(ccb.list.get(i).list.get(j).list.get(k).getCardNo())) {
                        num[0] = 1;    
                        num[1] = i;
                        num[2] = j;
                        num[3] = k;
                        return true;
                    }
                }
            }
        }
        
        return false;
    }

  比如在检查密码合法性时不用再次遍历各链表:

  

  public boolean checkPassword() {
        i = num[1];
        j = num[2];
        k = num[3];
        int password0;
        if(num[0] == 1) {
            password0 = ccb.list.get(i).list.get(j).list.get(k).getPassword();
            if(password == password0) {
                return true;
            }

        }
        else if(num[0] == 2) {
            password0 = icbc.list.get(i).list.get(j).list.get(k).getPassword();
            if(password == password0) {
                return true;
            }
        }
        return false;
    }

 

  9-7-1:

  类图:

  

  

  复杂度:

  

  、

  从类图来看,此次类间关系较8-7-1更为复杂,包含大量继承如DebitAccount、CreditAccount类对抽象类AbstractAccount的继承,以及CreditCard、DebitCard类对抽象类AbstractCard的继承等。此次作业较前次进一步增加许多实体类如ValidateData、GetBalance、Withdraw等实体类,进一步实现了单一原则。观察复杂度,达到了惊人的37,分析代码发现除了用于处理输入数据,遍历链表以外,更多的是因为判断跨行收取手续费以及贷记卡透支的情况,逻辑较为复杂,使用大量的if-else语句使得复杂度过高。

  由于此次题目是在已给源码基础上进行增改,包括创建抽象的账户及银行卡类来实现多态,此部分难度较易,所以解题思路主要是围绕如何计算各额外费,因此难度集中于用于存取款的Withdraw类。通过分析发现,校验卡存在性,密码正确性,ATM机存在性的方法依旧适用,但是原有判断余额是否足够的方法需要进行更改,因为业务中加入了贷记卡,存在透支功能。由于判断方法与存取款有一定的关联性,所以依据是否跨行将存取款功能分为两类。首先选择构建非跨行功能,因为业务逻辑比较简单,借记卡只有简单的收支和余额判断,贷记卡在此基础上增加的透支功能结合生活实际也有比较清晰的思路。然后选择构建跨行操作,由于跨行操作会带来相应手续费所以逻辑更为复杂,其中借记卡在跨行业务中难度也较易,着重分析下贷记卡:取款额外费用一般情况下=取款金额*跨行手续费利率+取款金额透支部分*0.05,所以余额是否足够的判断条件即为——(取款金额+额外费用)是否大于等于(账户余额+透支额度)。其次是贷记卡的存款功能,分析业务逻辑,存款同样需要收跨行手续费,之后要先还透支钱的部分,然后再增加账户余额 。

  注:考虑到现实中账户余额没有负数情况,所以进行透支操作时会将余额设置为0,将透支额度进行改变。具体代码如下: 

     double rate0 = aTM.getBank().getOverdraftrate();//透支
        double rate = aTM.getBank().getEnjambmentrate();//跨行
        double money;
        /**
         * 校验是否为跨行存取款
         */
        if (account.getBank().getBankNO() != aTM.getBank().getBankNO()) {
            
            if (cardtype.equals("DebitCard")) {
                if(amount < 0) {
                    amount = amount * (1 - rate);
                    account.setBalance(balance - amount);
                }
                else {
                    money = balance - amount * (1 + rate);            
                    if(money < 0) {
                        System.out.println("Sorry,your account balance is insufficient.");
                        System.exit(0);
                    }
                    account.setBalance(money);
                }
            }
            else {
                if(amount > 0) {
                    double n = amount * rate;//跨行手续费
                    
                    if ((n + amount) > balance) {
                        money = amount + n + (amount - balance) * rate0;
                        if((money - balance) > account.getQuota()) {
                            System.out.println("Sorry,your account balance is insufficient.");
                            System.exit(0);
                        }
                        account.setBalance(0);
                        account.setQuota(account.getQuota() - (money - balance));
                    }else {
                        account.setBalance(balance - (n + amount));
                    }
                }
                //存钱
                else {
                    amount *= (1 - rate);
                    if(balance == 0) {
                        if(account.getQuota() == 50000) {
                            account.setBalance(-amount);
                        }
                        else {
                            money = -amount + account.getQuota();
                            if(money >= 50000) {
                                account.setBalance(money - 50000);
                                account.setQuota(50000);
                            }
                            else {
                                account.setQuota(money);
                            }
                        }
                    }else {
                        account.setBalance(account.getBalance() - amount);
                    }
                }
            }
            
        }    
        else {
            if(cardtype.equals("DebitCard")){
                if (amount > balance) {
                    System.out.println("Sorry,your account balance is insufficient.");
                    System.exit(0);
                }
                account.setBalance(balance - amount);
            }
            else {
                if(amount > 0) {
                    if (amount > balance) {
                        money = (amount - balance) * (1 + rate0);
                        if(money > account.getQuota()) {
                            System.out.println("Sorry,your account balance is insufficient.");
                            System.exit(0);
                        }
                        account.setBalance(0);
                        account.setQuota(account.getQuota() - money);
                    }else {
                        account.setBalance(balance - amount);
                    }
                }
                //存钱
                else {
                    if(balance == 0) {
                        if(account.getQuota() == 50000) {
                            account.setBalance(-amount);
                        }
                        else {
                            money = -amount + account.getQuota();
                            if(money >= 50000) {
                                account.setBalance(money - 50000);
                                account.setQuota(50000);
                            }
                            else {
                                account.setQuota(money);
                            }
                        }
                    }else {
                        account.setBalance(account.getBalance() - amount);
                    }
                }
            }
        }

 

三、采坑心得

7-7-1:

  1、在书写代码时创建了两个Scanner的输入方式,在eclipse中能完整运行但是在PTA平台运行却没有显示结果,通过逐行排查代码发现此问题。原因未知,以后会注意。

  2、不了解getClass()的输出内容。在创建图形对象时第一次采用的getClass()来输出各图形名称:

    例:System.out.print(cardList.get(i).getShape().getClass()+ ":" + String.format("%.2f", n) + " ");

    

     然后改为:

    System.out.print(cardList.get(i).getShape().getShapeName() + ":" + String.format("%.2f", n) + " ");

    输出正常:

    

    最后通过查阅资料发现也可以通过getClass().getSimpleName()来得到预想的输出。

 

8-7-1:

  1、在处理输入数据时忘记对已有数据进行初始化:

    测试用例:   

  6222081502001312390 88888888 06 -500.00
  6222081502051320786 88888888 06 1200.00
  6217000010041315715 88888888 02 1500.00
  6217000010041315709  88888888 02 3500.00
  6217000010041315715
  #

    错误输出:
    

    更正,数据进行初始化:

    

    正常输出:

    

  2、对题目理解有误,错误理解题目为银行卡各自有金额,账户中金额=各个银行卡之和,通过测试用例发现,各银行卡是对相应账户余额进行操作。

 

9-7-1:

  1、跨行且透支计算费用理解有误,最开始理解为先计算跨行所需总费用,其次再计算由此透支产生的费用,事实上额外费用=跨行费用*跨行利率+ 取款费用透支部分 * 0.05。

    例:   

测试输入:
6640000010045442002 88888888 09 3000
6640000010045442002 88888888 06 8000
6640000010045442003 88888888 01 10000
6640000010045442002
#

输出样例:
业务:取款 张三丰在中国农业银行的09号ATM机上取款¥3000.00
当前余额为¥6880.00
业务:取款 张三丰在中国工商银行的06号ATM机上取款¥8000.00
当前余额为¥-1416.00
业务:取款 张三丰在中国建设银行的01号ATM机上取款¥10000.00
当前余额为¥-11916.00
业务:查询余额 ¥-11916.00

    对第二行输入数据

    错误算法:

      余额=-1*(8000*1.03-6880)*1.05=-1428

    正确算法:

      余额=-1*(8000*0.3+(8000-6880)*0.05+8000-6880)=-1416

 

  2、基础信息录入有误,没审好题,将新用户全设为农业银行,在进行测试时发现问题,后进行更正。

   

四、改进建议

  8-7-1:

  1、没有实现单一原则及开闭原则,需要对代码结构进一步优化。

  2、在确定卡的附属关系及通过卡获取账户、银行等信息时通过多重循环对各链表进行遍历,效率比较低且时间复杂度较高,可以采用题目集9中的双向链表,进而更高效的获取数据信息。

  

  9-7-1:

  1、在withdraw类中存取款的功能没有实现单一原则,在存取款功能中融合了判断账户余额是否足够的功能。应将判断功能单独写成一个判断方法。

 

五、总结

  1、通过题目集7的两次作业进一步熟悉了抽象类和ArrayList的使用方式。

  2、通过题目集8,进一步体会到如何去构思一个代码的框架,以及设计类间的关系。同时在思考银行业务逻辑时进一步锻炼了自己的逻辑思维。在处理输入数据时进一步熟悉正则表达式的语法以及常规运用逻辑。

  3、在题目集9中,通过理解老师所给源码,学会如何通过需求来判断是否需要将功能独立为一个类,同时也进一步体会到开闭原则,和单一原则的含义。在对原有代码进行多态优化时,也熟悉了抽象类的使用,同时学到了双向链表的好处。在构建存取款功能时,通过分析收费过程也进一步体验到真实的银行运作模式以及对思维能力有一定的锻炼。

  教学建议:

  通过参加反转课堂感觉有很多收获,因为要在规定的时间向其他同学清晰的呈现要求的板块一方面强化了自己在这一方面的学习,同时通过在同学面前讲述的方式也进一步锻炼了自己临场发挥能力,以及语言组织能力等较为实用的能力,希望老师在今后的教学中适当的多加入反转课堂的授课方式。

posted @ 2021-06-08 20:40  特立独行*  阅读(57)  评论(0)    收藏  举报