OO Unit1 总结


每次作业的思路和技术分析

No.1

一共写了8个类,2个接口,主要的其实只有4个类1个接口

主要接口:

  • PowerFunction就是每一项去掉系数的那一部分,有求导和乘法两个方法;

    4个主要的:
  • ItemMatcher负责将一行字符串解析;
  • PolynomialInput负责从指定的InputStream一行一行读,然后利用ItemMatcher实例解析结果,创建多项式;
  • SingleXPowerFunction实现了PowerFunction接口的类,表示只有一种未知数;
  • Polynomial类,维护了一个map,PowerFunction为键,系数为值。

不重要的类:

  • MainClass,包含main()和测试用例,单独弄出来方便测试;
  • PatternMarks是enum类,方便管理正则表达式的标记;
  • StringPatterns类专门用于正则表达式的生成,有一些静态的自动打包方法(添加捕获组、非捕获组、为捕获组设置标记);
  • MixUsingDifferentPowerFunctionException类是自定义的异常,是一个RuntimeException,保证多项式类只能选择一种实现了Powerfunction的类做键;
  • StringProcess接口是预处理的接口,实现一个String process(String)方法,会在读入的那个类,读入一行之后先对字符串进行预处理,再进行解析。

Metrics分析结果

类名 平均圈复杂度 总圈复杂度
Polynomial 2.5 30.0
PolynomialInput 2.0 14.0
SingleXPowerFunction 1.625 13.0
ItemMatcher 1.375 11.0
StringPatterns 1.143 8.0
MixUsingDifferentPowerFunctionException 1.0 4.0
MainClass 2.0 2.0
PatternMarks 0.0
Total 82.0
Average 1.745 10.25

复杂度较高的两个类Polynomial和PolynomialInput,确实都存在很大的问题。

  • 对于Polymial,问题在于输出逻辑过于复杂,不仅要判断来自内层的PowerFunction的形式,还要判断本身的正负,导致一个输出需要三个函数。(还因此导致了第一次作业互测的大量bug)
  • 对于PolynomialInput,问题主要是整体的设计。这个类需要将输入的InputStream作为参数,而本身以迭代器的方式向外输出一个个多项式。它利用ItemMatcher也只是解析Matcher,还要利用ItemMatcher返回的字符串数组信息来构建多项式。将输入一行字符串、拆分字符串、构建多项式全部耦合在一起了。

No.2

这一次作业我重新分析了整个多项式的数学性质,力求构建一个比较完备的多项式体系。经过分析发现多项式的运算有如下对应关系:

项*数 项+项
项^数 项*项

每行的一组运算都满足线性空间的运算要求的8条性质(第一列对应向量数乘,第二列对应向量加法),不过要保证这两个线性空间都有零元(而且是不一样的,前者是ZERO,后者是ONE)。其实数乘可以看作若干个加法,数幂可以看作若干个乘法。

构建一个比较完备的多项式系统的关键就是项的公共基类实现加法和乘法,我采用了如下策略:

  • 利用Monomial实现【乘法】维护一个Map<项,指数>
  • 利用Polynomial实现【加法】维护一个Map<项,系数>

Monomial和Polynomial也都是项类的子类。

这两个类都有化简方法,把Map中同类别的项拆开,只有一个key并且value为1时会退化为key,没有key时Monomial退化为ONE,Polynomial退化为ZERO。

实现运算的方法就是new一个相应对象,然后把被运算的对象一股脑放进Map中,再化简,然后上锁(不再允许更改)。

除了M和P两个类,还需要实现一些基础的类,项类理论上都最好是不可变对象,但是M和P一般创建的时候需要比较多的操作,所以给它们留了更改的接口,然后有一个加锁的方法(无解锁方法,加锁后尝试修改会引发RuntimeException)。

基础项包括:

  • 字母类:维护一个字符,有一个静态对象x
  • 常项类:ZERO、ONE两个静态对象
  • 三角函数类:一个sin\cos的标记,一个内部项的引用

具体实现上,有这样的细节:

  • 公共基类AbstractFunction是抽象类,实现了add(BigInteger),add(AbstractFunction),multiply(BigInteger),multiply(AbstractFunction),power(BigInteger)五个方法和为了方便的重载,这些方法都是返回一个新的对象。规定了一个抽象方法getDerivation求导;
  • 由于Polynomial和Monomial在行为上表现极其相似(都是用于实现某个线性空间上的运算),所以其大部分行为都可以提炼出来成为一个抽象的AbstractMutableFunction类;
  • 为了提高Polynomial和Monomial的安全性,为这两个类创建了static的工厂方法valueOf,创建并将所有待加入的项加入后,化简、上锁。注意工厂方法的返回值是AbstractFunction,因为化简可能会导致类型的改变(退化)。此外,将AbstractMutableFunction类所有可以改动map内数据的方法访问性改为protected;
  • 注意Polynomial参与乘法运算的时候,输出结果其实是Polymial,这要求Monomial类化简的时候,若map内有Polynomial,化简结果应该是Polynomial。

构建结果如下:

除了多项式体系之外,输入和构建部分大规模重构:

  • 弃用了原来的ItemMatcher(虽然没把它删掉);
  • 把以前的PolynomialInput改为InputAnalyzer,它接收一行的String为参数,不再负责输入。它不再负责多项式的构建,将这个任务交给了FunctionFactory。它仅进行一行字符串的预处理,而这些预处理由StringProcess进行。
  • 将以前的StringProcess接口更名为IStringProcess(其实是个C#命名风格),StringProcess改为一个实现了该接口的enum类,这里包括了检查格式、把原输入格式改为便于Factory处理的格式。通过InputAnalyzer+StringProcess的形式,使得预处理的执行和预处理的定义解耦,在后面要求更改后,只需在new的时候为InputAnalyzer指定其他的StringProcess组合即可,就像这样:
StringProcess[] processes = {
StringProcess.REMOVE_SPACE,
StringProcess.CHECK_FORMAT_TOTAL,
StringProcess.ADD_PLUS_AT_HEAD,
StringProcess.REPLACE_INDEX,
StringProcess.REPLACE_SYMBOL,
StringProcess.REPLACE_SYMBOL,
StringProcess.CHECK_FORMAT_POWER_LIMIT
};
InputAnalyzer analyzer = new InputAnalyzer(processes);
  • 更改了MainClass,它现在会先尝试寻找一个testSample.txt,若找到了,进入测试模式,从该文件读入全部测试数据,将输出结果输出,并开始记录日志;否则,从控制台进行读入,输出也只输出到控制台。

Metrics分析结果

类名 平均圈复杂度 总圈复杂度
polynomial.FunctionFactory 3.2 16.0
polynomial.functions.Polynomial 2.455 27.0
polynomial.functions.Monomial 2.417 29.0
polynomial.functions.AbstractMutableFunction 2.167 39.0
polynomial.functions.AbstractImmutableFunction 2.0 2.0
MainClass 1.75 7.0
polynomial.functions.Factor 1.625 13.0
polynomial.functions.IndexFunction 1.556 14.0
polynomial.functions.AbstractFunction 1.5 12.0
polynomial.InputAnalyzer 1.5 3.0
polynomial.StringProcess 1.429 10.0
polynomial.ItemMatcher 1.375 11.0
polynomial.functions.SinFunction 1.333 12.0
polynomial.StringPatterns 1.1 11.0
polynomial.functions.ModifyAfterLockException 1.0 4.0
polynomial.functions.Constant 1.0 7.0
polynomial.WrongFormatException 1.0 4.0
polynomial.PatternMarks 0.0
Total 221.0

复杂度较高的几个类都是承载着核心逻辑的类。不过FunctionFactory确实过于复杂了,将各个部分拆成一步一步的函数有点随意了。

No.3

由于有了第二次作业中很完备的多项式体系,第三次作业很顺利,主要的更改来自于输入逻辑和化简逻辑。

多项式体系基本没变:

对于输入逻辑,由于有了递归的结构,所以不能一把正则进行格式检查,并且sin只能嵌套因子,不能嵌套表达式,这使得格式检查要分为表达式和因子两类。这样使得预处理和构建表达式都需要同样的嵌套顺序,所以现在将预处理和构建表达式合并在一起解决,废弃FunctionFactory,将其中的逻辑改到InputAnalyzer中。InputAnalyzer主要由四个方法:表达式的预处理、表达式解析、因子预处理、因子解析。两个解析方法都只能被预处理方法调用,预处理方法可以被随意调用,以实现嵌套的解析。

由于目前的多项式在做乘法的时候,默认是需要进行乘法分配律拆括号的,这将导致多项式长度过长,需要再进行公因式提取。现在的算法是进行穷举,穷举所有可能的公因式提取的可能,然后比较输出结果的长度,选取最短的结果进行输出。此时就发现了原有的多项式系统不利于这样的化简,因为公因式提取是以a*x**b为单位进行的,但是原来的体系把数乘和数幂分开的,所以需要引进新的体系。但是好在这个体系不需要做求导之类的事情,提取公因式的的时候也只操作最外层的Polynomial,所以很简单就能构成了。

Metrics分析结果

类名 平均圈复杂度 总圈复杂度
polynomial.functions.shortner.SquareSinChecker 3.5 14.0
polynomial.FunctionFactory 3.2 16.0
polynomial.functions.Monomial 2.867 43.0
polynomial.InputAnalyzer 2.857 20.0
polynomial.functions.shortner.CommonFactorFinder 2.554 23.0
polynomial.StringProcess 2.4 36.0
polynomial.functions.Polynomial 2.316 44.0
polynomial.functions.shortner.CommonFactor 2.25 18.0
polynomial.functions.AbstractMutableFunction 2.211 42.0
polynomial.functions.shortner.Item 1.818 20.0
polynomial.functions.IndexFunction 1.8 18.0
MainClass 1.8 9.0
polynomial.functions.SinFunction 1.727 19.0
polynomial.OutputAnalyzer 1.667 10.0
polynomial.functions.SingleFactor 1.556 14.0
polynomial.functions.AbstractImmutableFunction 1.5 3.0
polynomial.functions.shortner.Factor.ConstantFactor 1.429 10.0
polynomial.functions.shortner.Factor.PowerFactor 1.286 9.0
polynomial.functions.AbstractFunction 1.125 9.0
polynomial.StringPatterns 1.083 13.0
polynomial.functions.shortner.SuperFactor 1.0 4.0
polynomial.functions.Constant 1.0 10.0
polynomial.functions.ModifyAfterLockException 1.0 4.0
polynomial.WrongFormatException 1.0 4.0
polynomial.functions.shortner.Factor 1.0 3.0
polynomial.PatternMarks 0.0
Total 415.0

三角函数的化简因为ddl之前都没有修完bug,所以就没有使用了,从分析结果来看,其复杂度太高,也是bug没有修完的原因。因为它不仅要检查AbstractFunction的内部嵌套的类,并检查是否是sin/cos,这导致其与原来的Functions系统每个类的耦合都很高。

FunctionFactory已经被弃用了,其代替品InputAnalyzer尽管还肩负着预处理的任务,但其表现比它好得多。

CommonFactorFinder需要穷举全部公因式,所以和Polynomial、Monomial耦合都不低。


bug分析

这一单元我自己的bug数量控制的还可以,三次作业一共只有2个bug,一个是第一次作业没有关注输出0的情况,一个是第三次作业优化过程中陷入死循环了。这两个bug都是边界条件所致。

这一单元我主动hack别人的表现确实不佳,我觉得很大的原因是因为我没有构建自动测试系统,全靠人力测试,是在太慢了。但是我人肉发现的bug,很多都是边界条件,比如指数的边界、化简为0的情况等。第一次作业就是没有考虑化简结果为0的情况,导致被hack10多次(但全是这一个问题,所以一次就修复了)。以后可能需要构建测试机了。


面向过程→面向对象

第一次作业虽然有点面向对象的想法,但是实际上还是面向过程的偏多一些。第二次作业从线性代数的这是种寻找对象的行为和交互方式,先不管本次作业的主要逻辑(求导),先构造好类的体系,设计好它们的加减运算。最后再根据业务逻辑,利用这些构造好的体系完成业务(具体分析过程写在No.2中了,就不复制了)。


收获

  • 总体结构要想明白了再敲键盘。由于第二次想明白了多项式的系统应该怎么构建,并且构造了一个足够完美的系统,使得,第三次在这一方面花费的精力大大减少(据我所知很多同学第三次作业都在重新构建这个系统花费了大量时间)。
  • 算法也要想明白了再敲键盘。提取公因式的算法就是因为最开始没有想好,导致频繁陷入死循环(无法停止穷举)。后来仔细思考了穷举的算法,利用队列一个一个检查,分析了新的算法不会出现重复的情况。
  • 从现实中的只是体系寻找类的行为方式、交互方式。这次作业我利用了线性代数的知识帮我构建了一个运算封闭的多项式系统,复杂、功能多,同时bug又少。
  • 这辈子都没有过在这么短的时间内写这么多代码的经历。(上次这么多还是基物实验课的虚拟仿真实验的设计,但是这个写了好久呢)

OO Unit1 总结的更多相关文章

  1. 多项式求导系列——OO Unit1分析和总结

    一.摘要 本文是BUAA OO课程Unit1在课程讲授.三次作业完成.自测和互测时发现的问题,以及倾听别人的思路分享所引起个人的一些思考的总结性博客.本文第二部分介绍三次作业的设计思路,主要以类图的形 ...

  2. OO unit1 summary

    Unit 1 summary 一.前言 三周左右的学习,OO第一单元顺利结束了,个人认为有必要写个blog来反思总结一下自己第一单元的学习情况,以便更好地进行后面的学习. 之前从来没有写blog的习惯 ...

  3. OO第四单元总结暨期末总结

    OO第四单元总结暨期末总结 目录 OO第四单元总结暨期末总结 第四单元三次作业架构与迭代 整体感受 HW1 HW2 HW3 四个单元架构设计与方法演进 Unit1 Unit2 Unit3 Unit4 ...

  4. 「BUAA OO Unit 4 HW16」第四单元总结与课程回顾

    「BUAA OO Unit 4 HW16」第四单元总结与课程回顾 目录 「BUAA OO Unit 4 HW16」第四单元总结与课程回顾 Part 0 第四单元作业架构设计 架构设计概要 AppRun ...

  5. GLUT的简洁OO封装

    毕业设计用到了OpenGL,由于不会用MFC和Win32API做窗口程序:自然选用了GLUT.GLUT很好用,就是每次写一堆Init,注册callback,觉得有点恶心,于是对他做了简单的OO封装.记 ...

  6. Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结

    Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结 1. Sql语言应该得到更大的范围的应用,1 1.1. 在小型系统项目中,很适合存储过程写业务逻辑2 1.2. 大型 ...

  7. OO中,先有对象还是先有类?

    就是问,在面向对象思想里,先有对象还是先有类,乍一看和先有鸡蛋还是先有鸡是一类问题,其实不然!这个问题,在lz考研复试的时候被面试官问过,一模一样,如今又在一个笔试题里看到了类似的题目,眨一下,有人会 ...

  8. OO方式下,ALV TREE和ALV GRID的不同之处

    作为大部分报表程序的基础,ALV GRID差不多是每个ABAP开发者必须了解和掌握的内容,因此网上也不乏相关资料,而ALV TREE的应用相对较少,中文资料也就比较少见了.实际上,ALV TREE和A ...

  9. 从人类社会的角度看OO(独家视角)

    引言 在OO的工作中,我们一定会涉及到类,抽象类和接口.那么类和抽象类以及接口到底扮演的什么角色? 本文主要是从人类社会的角度阐述类与抽象类以及接口的"社会"关系,从而让我们抛弃书 ...

随机推荐

  1. 看完我的笔记不懂也会懂----Node.js

    Node.js 学习 - 命令行窗口 - 进程与线程 - ECMAScript的缺点 - Node模块化 - Node中的全局对象 - 包 package - NPM包管理器 (Node Packag ...

  2. 抽一根烟的时间学会.NET Core 操作RabbitMQ

    什么是RabbitMQ? RabbitMQ是由erlang语言开发的一个基于AMQP(Advanced Message Queuing Protocol)协议的企业级消息队列中间件.可实现队列,订阅/ ...

  3. 【老孟Flutter】Flutter 2.0 重磅更新

    老孟导读:昨天期待已久的 Flutter 2.0 终于发布了,Web 端终于提正了,春季期间我发布的一篇文章,其中的一个预测就是 Web 正式发布,已经实现了,还有一个预测是:2021年将是 Flut ...

  4. 【ZeyFraのJavaEE开发小知识03】@DateTimeFomat和@JsonFormat

    关于在Element UI的el-dialog组件中使用echarts的问题 问题描述: "Cannot read property 'getAttribute' of null" ...

  5. freebsd升级时出错,没有ntp用户解决

    freebsd升级出错,没有ntp用户 终端执行命令 pw groupadd ntpd -g 123 pw useradd ntpd -u 123 -g ntpd -h - -d /var/db/nt ...

  6. slickgrid ( nsunleo-slickgrid ) 3 修正区域选择不能跨冻结列的问题

     slickgrid ( nsunleo-slickgrid ) 3 修正区域选择不能跨冻结列的问题 上次解决区域选择不能跨冻结列问题的时候,剩了个尾巴,从右往左选择的时候,会出现选择不正常的情况,后 ...

  7. EF Core 源码分析

    最近在接触DDD+micro service来开发项目,因为EF Core太适合DDD模式需要的ORM设计,所以这篇博客是从代码角度去理解EF core的内部实现,希望大家能从其中学到一些心得体会去更 ...

  8. Java字符串==和equals的区别

    首先我们来了解一下String类,Java的字符串是一旦被赋值之后无法更改的(这里的无法更改是指不能将字符串中单个或一段字符重新赋值),这也是Java虚拟机为了减少内存开销,避免字符串的重复创建设立的 ...

  9. 【博弈论】组合游戏及SG函数浅析

    目录 预备知识 普通的Nim游戏 SG函数 预备知识 公平组合游戏(ICG) 若一个游戏满足: 由两名玩家交替行动: 游戏中任意时刻,合法操作集合只取决于这个局面本身: 若轮到某位选手时,若该选手无合 ...

  10. 2、MyBatis教程之第一个MyBatis程序

    3.MyBatis第一个程序 1.搭建实验数据库 CREATE DATABASE `mybatis`; USE `mybatis`; DROP TABLE IF EXISTS `user`; CREA ...