面向对象程序设计前三次作业回顾(一)

发布时间 2023-03-22 21:16:09作者: 一杯小丞汁

一、前言

前三次作业的题量在整个OOP课程当中属于题量较小的情况,但对于初次接触面向对象的我来说,这些题也花费了不少的时间来完成。难度上来说,这三套题集都有明显的难度梯度划分,从前往后的难度是从易到难,在每个题目集内部难度分布也是如此,符合课程学习的一般规律。三次题目集整体难度不高,主要是为了让我们快速了解和熟悉一些基本的java语法,以及一些常用的类。现对前三次题目集的知识部分进行回顾,将涉及到的知识点尽可能全面地罗列出来。

(一)知识点:

    • 代码风格需遵守阿里巴巴Java开发手册的编码规范,例如:
      if(A<20&&A>0)
              {
                  cost=12+(A-1)*2;
              }
      // 不符合标准的格式:运算符之间无空格、if无空格且其后第一个花括号单独占一行、变量名太随意,无法做到 “见名知意”、夸张的缩进
      if (weight < 20 && weight > 0){
          cost = 12 + (weight - 1 ) * 2;
         }
      // 符合标准的格式
    • Java导入工具包的方式及常见方法所属的工具包,例如:Scanner类需要用 import java.util.Scanner; 导入
    • 对象的概念
    • 类的概念及如何自定义类并实现一些简单功能,例如:
      class Circle {
          private double radius;
      public double getRadius() {
              return this.radius;
          }
      
          public void setRadius(double radius) {
              this.radius = radius;
          }
      
          public double getS() {
              return (Math.PI) * this.radius * this.radius;
          }
      }
      // 以上代码创建了一个圆形类,具有一个私有的double类型属性半径,在类外可通过公共的方法获取半径、圆的面积、修改半径
    • List集合下的字符串类StringBuffer的常见方法(后面的课程中介绍了StringBuilder和StringBuffer的区别,限于作者当时刚开始学习Java,首次使用字符串时用的是StringBuffer),例如截取、比较等等。字符串这部分内容和C语言有不小的区别
    • 泛型的概念
    • List集合的迭代器遍历方式

 


 

二、设计与分析

    • 题目集3的第三题和第四题的综合性在所有题目中最强,基本涵盖了前两套题目集中的所有知识点,因此这里作者只分析这两题

(一)题目集3,第3题:

1. 原题放送:


 2. 题目分析:

这道题由于给出了我们Date的类图,因此不需要我们自己设计,只需要根据类图将类实现即可,难度不高,重点在于正确识读类图

需要注意的是这个类有两种构造方法,可进行重载,一种构造方法是默认的,另一种构造方法是传入year,month,day进行初始化

        3. 代码设计:

  1 import java.util.*;
  2 
  3 public class Main{
  4     public static void main(String args[]){
  5         Scanner in = new Scanner(System.in);
  6         Date date = new Date(in.nextInt(), in.nextInt(), in.nextInt());
  7         if(date.checkInputValidity()){
  8             date.getNextDay();
  9             if(date.checkInputValidity()){
 10                 System.out.printf("Next day is:%d-%d-%d\n",date.year,date.month,date.day);
 11             }
 12         }
 13     }
 14 }
 15 
 16 class Date {
 17     int year;
 18     int month;
 19     int day;
 20     int[] mon_maxnum = new int[]{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 21 
 22     public Date() {
 23     }
 24 
 25     public Date(int year, int month, int day) {
 26         this.year = year;
 27         this.month = month;
 28         this.day = day;
 29     }
 30 
 31     public int getYear() {
 32         return year;
 33     }
 34 
 35     public int getMonth() {
 36         return month;
 37     }
 38 
 39     public int getDay() {
 40         return day;
 41     }
 42 
 43     public void setYear(int year) {
 44         this.year = year;
 45     }
 46 
 47     public void setMonth(int month) {
 48         this.month = month;
 49     }
 50 
 51     public void setDay(int day) {
 52         this.day = day;
 53     }
 54 
 55     public boolean isLeapYear(int year) {
 56         boolean flag = false;
 57         if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
 58             flag = true;
 59         }
 60         return flag;
 61     }
 62 
 63     public boolean checkInputValidity() {
 64         boolean flag = true;
 65         if (this.year < 1900 || this.year > 2000 || this.month < 1 || this.month > 12) {
 66             flag = false;
 67         } else if (isLeapYear(this.year)) {// 将闰年和非闰年情况分类讨论
 68             if (this.month != 2) {
 69                 if (this.day < 0 || this.day > mon_maxnum[this.month]) {
 70                     flag = false;
 71                 }
 72             } else {
 73                 if (this.day > 29 || this.day < 1) {
 74                     flag = false;
 75                 }
 76             }
 77         } else {
 78             if (this.day < 1 || this.day > mon_maxnum[this.month]) {
 79                 flag = false;
 80             }
 81         }
 82         if(flag == false){
 83             System.out.print("Date Format is Wrong\n");
 84         }
 85         return flag;
 86     }
 87 
 88     public void getNextDay() {
 89 
 90             if (this.month == 12 && this.day == 31) {// 最后一天的下一天
 91                 this.year++;
 92                 this.month = 1;
 93                 this.day = 1;
 94             } else if (!isLeapYear(this.year)) {// 非最后一天的平年的下一天
 95                 if (this.day + 1 > mon_maxnum[this.month]) {
 96                     day = 1;
 97                     this.month++;
 98                 } else {
 99                     this.day++;
100                 }
101             } else if (isLeapYear(this.year)) {// 非最后一天闰年的下一天
102                 if (this.month == 2) {
103                     if (this.day + 1 > 29) {
104                         day = 1;
105                         this.month++;
106                     } else {
107                         this.day++;
108                     }
109                 } else {
110                     if (this.day + 1 > mon_maxnum[this.month]) {
111                         day = 1;
112                         this.month++;
113                     } else {
114                         this.day++;
115                     }
116                 }
117             }
118         }
119     }
我的代码
    • 在实现 getNextDay() 这个方法时,调用了isLeapYear() 方法,根据是否为闰年,把日期的进位情况分类讨论

三、 踩坑心得

这是作者第一次提交的结果

从测试点不难看出,平台进行了多个无效值测试,其中有一个无效值是我在设计checkInputValidity() 方法时没有考虑到的,通过多次尝试,摸索出了该非法边界值。

测试用例:

1900 1 0

输出结果:

Next day is:1900-1-1

显然,1900年1月0日是一个错误的输入。由于该类内部方法采用的是模块化设计,我们很快就能根据各个方法的职责定位到有问题的代码,即checkInputValidity() 方法,以下是作者最初的checkInputValidity() 方法。

 1 public boolean checkInputValidity() {
 2         boolean flag = true;
 3         if (this.year < 1900 || this.year > 2000 || this.month < 1 || this.month > 12) {
 4             flag = false;
 5         } else if (isLeapYear(this.year)) {
 6             if (this.month != 2) {
 7                 if (this.day < 0 || this.day > mon_maxnum[this.month]) {
 8                     flag = false;
 9                 }
10             } else {
11                 if (this.day > 29 || this.day < 1) {
12                     flag = false;
13                 }
14             }
15         } else {
16             if (this.day < 0 || this.day > mon_maxnum[this.month]) {
17                 flag = false;
18             }
19         }
20         if(flag == false){
21             System.out.print("Date Format is Wrong\n");
22         }
23         return flag;
24     }

根据上面的测试用例,推断是第7行和第16行  if (this.day < 0 || this.day > mon_maxnum[this.month])  出现问题,我们仔细看一下这个逻辑,一个满足 0 <= this.day <= mon_maxnum[this.month]  的 day 的值被认为是合法的,则day = 0也合法。这不符合日期的逻辑,因此只需要修改为  if (this.day < 1 || this.day > mon_maxnum[this.month])  即可

 


 

四、改进建议 

(一)遵循SRP原则

以下面checkInputValidity() 方法为例:

 1    public boolean checkInputValidity() {
 2         boolean flag = true;
 3         if (this.year < 1900 || this.year > 2000 || this.month < 1 || this.month > 12) {
 4             flag = false;
 5         } else if (isLeapYear(this.year)) {
 6             if (this.month != 2) {
 7                 if (this.day < 0 || this.day > mon_maxnum[this.month]) {
 8                     flag = false;
 9                 }
10             } else {
11                 if (this.day > 29 || this.day < 1) {
12                     flag = false;
13                 }
14             }
15         } else {
16             if (this.day < 1 || this.day > mon_maxnum[this.month]) {
17                 flag = false;
18             }
19         }
20         if(flag == false){
21             System.out.print("Date Format is Wrong\n");
22         }
23         return flag;
24     }

第20~22行的输出放在这个方法里不太符合单一职责原则,“输出”的职责不应该放在 “判断输入是否合法” 的方法中,

符合SRP原则的编码方式应该是在主函数中根据checkInputValidity() 的返回值决定是否执行 System.out.print("Date Format is Wrong\n"); 。