星河故里

导航

OOP第二次总结

1.前言

1.第四次作业共三道题,题量不多,但有一定难度,后两道题均为之前作业的迭代式要求,新增代码的聚合类设计,继承类设计,难度不高,逻辑清晰,只需要在原本代码的基础上将其进行重构即可,耗时在30分钟内,此次题目集最大的boss一如既往的是正则表达式的花式玩法(正则写的好,解题没烦恼~),在这道题上前前后后有十多个版本,但还是有些细节没处理好,耗时6h;

image


2.第五次作业一共五道题,题量中等,但7-1,7-2,7-3难度不高,且逻辑清晰,都是只需要写一个方法即可完成,耗时30min,7-5又是对日期的另外一种设计,类之间的联系没有题目四那么紧密,难度中等,只需要跟着类图代码重构即可,耗时30min。关于java关键字的统计,这道题的难度同样在于正则表达式的书写,但是其中还有一个点在于如何统计出现的次数,这个刚开始确实没有太清晰的思路,每次写java的时候,我们写关键字,系统都会自动识别,但是确没想过,他们到底是怎么实现的,处在什么位置的词才可以称之为关键词,我们应该运用什么样的数据结构来储存关键字和关键字出现的次数,只有想明白了这些,写这道题的时候才不至于那么懵的(耗时3h)

image


3.第六次作业一共六道题,整体难度偏低,7-1,7-3,7-4考查的均为正则表达式,7-5与7-6是在前面出现的题目上的代码重构,这里的思考需要一些时间,在对着类图进行Coding时,思路会被带着 走,那么之后的实验如果是需要对我们自己写的代码进行优化重构,我们应该从哪几个方面去思考(整体耗时2.5h);

image


2.设计与分析

①题目集4(7-2)、题目集5(7-5)两种日期类聚合设计的优劣比较

题目集4(7-2)日期问题面向对象设计(聚合一)

类图如下所示

image

相比于题目集7-5,在DateUtil类下只需要创建一个Day类对象,就可以将所有的信息联系起来,但是调用方法的层次比较多,调用起来相对复杂,此聚合设计Year,Month,Day三类对象之间都产生了耦合联系,在信息传递的过程中(Year->Month->Day),任意一个对象的属性产生异常,在DateUtil中都无法正常处理数据,且查找异常的过程中,相对繁琐,如果按照此类聚合设计,在类的种类比较多的情况下比较难处理出现的异常。


题目集5(7-5) 日期问题面向对象设计(聚合二)
类图如下:

image

相比于题目集7-3的设计,此类设计保持了类的单一职责性原则,在DateUtil中设计需要处理相关类对象时,调用的方法相对简单,类与类之间在设计层面上是没有任何联系的,在此类设计的基础上,可以进行横向扩展,且无需改动原本的代码,在题目集7-3的中,如果想增加一个类(例如操作日志:其中记录了操作的时间以及操作的事件),就需要找到适合的插入点,插入这个类,建立日志类与其他类之间的关系,那么在设计过程中这个逻辑就相对繁琐,在题目集7-5中,只需要在现有基础上加一个log类,属性为String类型的操作事件,同时,可以新建一个处理日志和Date的类(例如叫DealLog类),其中包含DateUtil对象与log对象,类似于日常生活中中介的作用。

②题目集4(7-3)、题目集6(7-5、7-6)三种渐进式图形继承设计的思路与技术运用(封装、继承、多态、接口等)
题目集4(7-3)图形继承
题目需求如下:

image

类图如下

image

题目集6(7-5)图形继承与多态
需求设计如下:

image

类图如下

image

题目集6 (7-6)实现图形接口及多态性
类图如下

image

三种渐进式图形继承设计的思路与技术运用(封装、继承、多态、接口等)
分析:7-3图形继承,所有抽象描述图形均继承于抽象类Shape,此题设计将图形分为了三个层次,总类Shape,边类Rectangle,Circle,详细物体类:Box,Ball;从上到下,从抽象到具体。在题目7-4中,除了基本的图形继承外,还在其中封装了validate()方法,且在处理整体面积时,要求运用到多态,因为在设计存储对象的数组时,Circle,Rectangle,Triangle是三种不同的类,且不清楚输入的各个类型的对象到底有多少,但是他们之间唯一的联系就是共同的父类Shape,那么此时在Dealdata类中,就需要创建全部装Shape类的数组了,同时,在调用迭代时,也可以利用这个特性,进行后续操作.

private ArrayList arrayList = new ArrayList<>(); //存放图形对象的数组


例如求各个图形的面积和
public double sumArea(){
Iterator iterator = arrayList.iterator();
double sum = 0;

    while (iterator.hasNext()){
       sum += iterator.next().getArea();
    }

    return sum;
}

另外,在题目集7-6中,采用的设计思路是,通过一个共同实现的接口将Rectangle类与Circle类联系在一起,求图形的面积。

③对三次题目集中用到的正则表达式技术的分析总结
//题目集4(水文信息校验)

interface RegexMatcher{
//正则表达式
//时间部分
String TimeRegex = "([1-9][0-9]{0,3})\/([1-9]|[1][0-2])\/([1-9]|[1-2][0-9]|[3][0-1])";
String SpaceRegex = "[ ]";
String HoursRegex = "([0,2,4,6,8]|[1][0,2,4,6,8]|[2][0,2])\:(00)";
String Complex ="("+ TimeRegex+SpaceRegex+HoursRegex+")";
//目标水位,实际水位,流量
String waterLevelOrFlow = "([1-9][0-9]{0,2}((\.[0-9]{1,3})|()))";
//目标开度,实际开度
String singleOpening = "([1-9]\.[0-9]{2})";
String Opening = "([1-9]\.[0-9]{2}\/[1-9]\.[0-9]{2})";
//整条数据
String wholeInfo = "([ ]"+Complex+"[ ]\|[ ]"+waterLevelOrFlow+"[ ]\|[ ]"+waterLevelOrFlow+"[ ]\|[ ]"+Opening+"[ ]\|[ ]"+waterLevelOrFlow+"[ ])";
}

在书写与设计正则表达式的过程中,需要对所有可能出现的情况做一个概括与总结,这样可以使得书写的正则表达式更加简洁凝练,任何复杂的正则表达式向下细化到一定程度,就是一类类简单的情况:例如题目集4(水文信息校验)中的wholeInfo,内部包含时间校验、目标水位、实际水位、流量等校验,如果按照题目要求写一个完整的正则,测试过程会比较复杂,此时我们就需要将各个部分细化,分别写出对应的正则表达式,再进行分块测试,测试无误后再进行组合;

另外,在书写关于数字的正则时,需要对可能出现的情况进行一个规律概括,例如

String PositiveNum = "([+]?(([1-9][0-9])(\))?x(\[1]?[1-9][0-9])?)|[+]?([1-9][0-9])"; //大于0的整数or幂函数
String NegativeNum = "-((([1-9][0-9])(\))?x(\[2]?[1-9][0-9])?)|-([1-9][0-9]*)"; //小于0的整数or幂整数
这里对大于0的正整数,首先进行分析,分为一位数和多位数(位数大于1),在位数为一位时,不能为0,即[0-9],在位数大于1时候,第一位数(数位最大的数字不能为0),综合分析,得到的正则表达式为[1-9][0-9]*,所以,所需要的正则可以为:([+]?)([0-9]|[1-9][0-9]*)

同时,java中也有更方便的概述,叫做java匹配字符集,就相当于其他人帮你总结的规律,你只需要在需要的地方写入使用就行了。
image

//题目集5 java关键字校验

String explainReg = "(\/\/(.))|(".")|(\/".*\/")";
String[] spilt = totalString.split("[ ()\n\t\r,;\[\]{}\<\>.:]");

//题目集6

1.QQ号:String Regex = "[1-9][0-9]{4,14}";
2.验证码:String Regex = "\w{4}";
3.学号:String Regex = "(2020)((1[1-7])|(7[1-3])|(8[1-2]))(([0][1-9])|([1-3][0-9])|(40))";

④题目集5(7-4)中Java集合框架应用的分析总结

Java关键字出现次数判断中用到的集合框架:List,Map,Arrays


其中List类型的变量用来装所有的关键字,顺序数组,其中Map<String,Integer>中的两个变量类型就类似于函数的自变量和因变量,分别为Key和Value,一个Key只能对应一个Value,但是一个Value可以有不同的Key,这里就联想到了关键字是不会变化的,一直在变化的是统计的Key的个数,就是这儿的Value,所以这里把关键字作为Key,关键字出现的次数作为Value,在对所有输入的源码进行相应处理后,把他们看作单个的字符串来处理(对于同一行的代码,如果为注释,可以直接将此行进行删除处理,这样也简化了后面的统计)

image

使用Map框架中的containsKey来判断是否为第一次出现该关键词(是返回true),外层循环遍历关键字表,若为第一次出现,即可将Key对应的value设置为1,其他情况就在原有的value上进行自加操作,即可完成相应的关键词统计.(这儿的split数组是将代码里的所有单词挨个抽取出来存入之后的数组,)

String[] spilt = totalString.split("[ ()\n\t\r,;\[\]{}\<\>.:]"); //totalString:所有输入的源代码(正则内包含了几乎所有的单词之间的分割符);

最后使用Map中的KeySet()方法获取Key视图,并对其(关键字),进行排序Arrays.sort();
在使用框架之前应分析我们所需要达到的效果和目前选择的集合框架特点是否最大程度匹配,例如:需要存储一些相同类型的元素,允许且含有重复,如果此时选用了Set集合,那么其中的重复数据就会被一次次的覆盖,如果添加若干个相同的数据,最后其中存储的就只有最后一个元素了,集合长度size()的返回值也为1

public class Main {
    public static void main(String[] args){
        Set set = new HashSet<>();
        set.add("A");
        set.add("A");
set.add("A");
        System.out.println(set+"\tsize:"+set.size());
    }
}
运行结果如下
image

这时就应该采用List(允许相同元素),

public class Main {
    public static void main(String[] args){
        List set = new ArrayList<>();
        set.add("A");
        set.add("A");
        set.add("A");
        System.out.println(set+"\tsize:"+set.size());
    }
}
运行结果如下
image

与此同理,如果要存储不同的元素,就不应该使用List,转之使用Set;

image


3.采坑心得

编写水文数据题设计时候,任务书中要求的是LocalDateTime类型,在设计之初,因为考虑到在处理输入的一整条水文数据都是在处理字符串,通过字符串的split方法,将一条完整的数据分成我们所需要的,在处理测量时间时,我并没有按照设计书的参考要求,而是将其设置为String类型的数据放置在class HydrologicalInfo中,对测量采集时间的合法性检测中,如果需要对年月日进行检测,

private String measureDateTime; //测量时间
image
那么实际上,想利用正则表达式来判断输入的年月日是否正确,事实上是比较冗余的,而且处理的并非是数字,所以这个方法是太不适用的(但是如果要硬刚的话,那就得把合法日期的所有情况都考虑到了),导致后续设计中无法对其进行合法性校验,唯一能合法性判断的就只有输入的这一字符串是否合String TimeRegex = "([1-9][0-9]{0,3})\/([1-9]|[1][0-2])\/([1-9]|[1-2][0-9]|[3][0-1])";
image

那么这个测试点就自然过不了了

image

后续于是对设计层失误做出如下改进:

  1. 将measureDateTime改为LocalDateTime类型
    image
  2. 写出正确的日期(其中包含,年月日与具体时间)的所有正确情况

String regexDate = "((((([1-9]0-9)|((1-9))|(([13579][26])|([2468][048]))|([48]))|(((([13579][26])|([2468][048]))|[48])00))/2/(([1-9])|(1[0-9])|([2][0-9])))|(([1-9][0-9]{0,3})/((([13578]|1[02])/([1-9]|[12][0-9]|3[01]))|((([469]|11)/([1-9]|([12][0-9])|(30)))|(2/(([1-9])|(1[0-9])|([2][0-8]))))))) ((([02468])|([1][02468])|(2[02])):00)";

3.对代码进行部分重构

操作结果:
(可以对错误日期进行检测)

image

(可以对全为null值进行检测)

image

4.改进建议

1.代码结构设计改进:尽量遵守设计模式六大原则:
1、开闭原则(Open Close Principle)
2、里氏代换原则(Liskov Substitution Principle)
3、依赖倒转原则(Dependence Inversion Principle)
4、接口隔离原则(Interface Segregation Principle)
5、迪米特法则(最少知道原则)(Demeter Principle)
6、合成复用原则(Composite Reuse Principle)

在日期类与图形类设计迭代过程中,由一开始的类耦合性强到最后的实现开闭原则,合成复用,接口隔离,不仅结构更加清晰,而且可重用代码、让代码更容易被他人理解、保证代码可靠性,那么这其实也是重构的过程,以后在编码写题的过程中,在用最简单的"方法"解题之后,需要多提出另外的设计结构,在不同的情况下,上述设计模式也并非都是最适合的,在对比的过程中,我们可以逐渐总结出什么样的设计模式偏向于何种类型的编码结构。


5.总结

通过本阶段三次题目集的综合训练,学到了一些比较优良的设计模式,体会到了这些设计模式给面向对象程序设计带来的好处(耦合性低,可扩展性强,代码复用性高),同时也在正则表达式的归纳总结上有了进一步的提升,从一个复杂的判别标准式,分块分析测试,到最终的整合成完整,在这个过程中能够考虑到大部分的情况,并能在整合中测试出一些考虑缺陷。在这关于正则表达式的运用题目中,编写表达式花费时间都是最长的,我总结了几个原因:1.对正则表达式的转义符使用不当,导致判断多次失败 2.括号分块逻辑不够清晰,正则代码冗余。3.对所判断数据要求分析不到位,分析花费时间较长。除此之外,能够运用基本的集合框架解决问题,但是在数据结构之间的转换还不够熟练,toArray()、KeySet()等这一类的方法,之前使用较少,使用不熟练。这些都是在以后的学习中需要进行进一步的学习和研究。


  1. +- ↩︎

  2. +- ↩︎

posted on 2021-04-30 17:36  星河故里  阅读(74)  评论(0)    收藏  举报