菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
80
0

BUAA 面向对象课程 第一单元总结

原创
05/13 14:22
阅读数 19197

BUAA 面向对象课程 第一单元总结

面向对象设计与构造 第一单元已经结束,在此进行总结。

第一次作业

综述

第一次作业由于是简单表达式的求导,不含有复杂因子,不含有嵌套的表达式因子。那么这个结构就可以简单的描述为:

表达式 由 若干项 构成

那么结合正则表达式工具的应用,对输入的表达式进行拆分处理。每当输入一个项,利用正则将这个项拿出来,放到相应的对象中进行解析。

这个解析的思路是清晰的,由于难度不大,为了快速写出本次作业,设计中未考虑对嵌套需求的增量支持。

基于度量来分析程序结构

类图:

image

其中,RegConst存储正则表达式。
对于Form类,设计考虑是这样的。在本次作业不考虑嵌套需求的增量支持下,为了支持后续可能出现的新函数(如三角函数等),特意设计了Form类。对于每个简单项,最终都能化为标准形式。在本次作业中,标准形式即指a*x^b。其中,决定是否是同类项的因素是除了系数a之外的其余数,本次作业中即为b。那么,把这些因素抽象出来,构成一个Form类。(本次作业中,由于只有b决定了是否为同类项,若不考虑可能出现的新函数,Form类是可以不抽象出来的)
其余类的含义为综述中所描述。

复杂度分析

方法复杂度

image

总体来讲复杂度较为分散,但Term类构造方法及toString方法的圈复杂度和设计复杂度较高。对于构造方法,设计中是传入字符串,直接将其转换并同时合并了同类项。因将化简的过程都放入了构造中,导致的复杂度过高。而对于toString方法,由于是在输出环节对相应的特殊情况进行优化处理,导致有较多的分支判断在这个方法中,无法避免。这样的好处是结构简单,写代码是更加方便,缺点是会不可避免的增加复杂度。

类复杂度

image

Term类复杂度稍高,在方法复杂度中有分析。

其余类复杂度情况较为良好。

优缺点分析

优点

  • 将整条表达式分解为若干项,每提取出一个项就进行处理,避免了过于巨大的正则表达式,同时也不会出现 使用完整大正则 直接解析整个多项式而造成的时间/空间巨大消耗。
  • 整个逻辑结构清晰,代码易于构建,同时只要没有逻辑bug,在检查过了细节问题后,便能保证程序的正确性以及健壮性。
  • 合并同类项的优化是基础的,效果也不错,

缺点

  • 缺点也是明显的,未留有对嵌套表达式增量开发的余地。
  • 为了代码编写的效率,牺牲了部分模块设计,将方法耦合在一起。这样耦合度控制的就不好。

第二、三次作业

综述

由于从第二次作业开始,便加入了对嵌套表达式的处理。于是抛弃掉第一次作业的结构,进行新的设计。这里二三次作业实现的效果很好的增量开发,故将这两次作业放在一起进行叙述。

基于度量来分析程序结构

类图:

image

结构设计:

  • Factor:抽象类,万物基于Factor。内部声明了三个方法 simplify(), derivation(), print()

  • Poly: 多项式,其中的优化方法simpify()下述

  • Term: 项

  • 可理解为基类:SinCosPowerConst(其中Power仅指代x^n幂函数)

  • Parser:将递归下降过程封装在此类中

对解析类(Parser)单独解释:

image

采用递归下降法,构建树结构。此方法构建完成后的树严格满足如下结构:Poly - Term - 基类|(Poly - Term - ....)。也就是说,parse()后建成的树结构中,Poly内部的容器中存的必定是Term,Term内部容器中存储的必定是基本因子或者表达式因子。(而在simplify之后,才可能会把某些元素向树结构中的浅层提,如下图中所描绘的)

对每个类中共有的三个方法进行解释:

  • simplify()

    • 有如下几种功能:
    • 对当前元素(本对象)进行同类项合并的化简,对容器内(若本对象含有容器)的元素进行检索,若含有类似于 一个Term元素,但此Term中仅有一个因子的情况,进行 ”抽丝剥茧“,将这个单独的因子提取出来,直接取代Term的位置。
      image
    • Term中进行简单Power,Const的合并
    • Poly中进行简单Const的合并。这是微优化,但是却很重要,因为表达式中会出现很多的常数项,幂函数项
  • derivation():求导。对于基类直接求导,对于Poly意味着加法,Term意味着乘法、链式求导。

  • print():实际上返回String,对于树结构逐层返回出正确格式的字符串。

总体解释:

整体流程类似于:

Parser.parse()
simplify()
derivation()
simplify()
print()

代码复杂度分析:

方法复杂度:

image

由于method总数有60余个,完全截图的话图片太长,故只截取飘红的部分

Parser.getTrig()方法飘红,由于把对Sin和Cos的提取放到了一起,导致条件判断语句稍多了一些,逻辑还是比较清晰的。

Parser.getFactor()因为因子有四种,用了几个if语句判断。同时表达式因子左右的括号也用了if,相当于在if中又嵌套了一个if。

Const.valueOf()是一个静态工厂方法,本身复杂度不应该高,但是我在Const类中缓存了常用的几个对象(0, -1, 1),导致用了几个if来特判需要产生的对象是否已缓存,显示飘红,实际没有问题。

App.main()是入口方法,我在main方法中对式子进行了一些init操作,并没有写到单独的方法中,所以导致了main方法飘红。

对于其他方法的复杂度,耦合性,都在合适的范围内,符合预期要求。

类复杂度:

image

可以预见的,Parser类复杂度较高,因为在设计的过程中给,Parser类用了递归下降分析法,对某个元素的提取写在了Parser类的某个单独的方法中,同时充斥着无法避免的条件判断,字符特判。这导致了本类的复杂度很高。

而排除对表达式的解析外,其余类的复杂度都比较好,总体基本符合高内聚低耦合。第一次作业中Term类飘红的情况本次也没有出现(两次架构不一样也没有可比性)

分析自己程序的Bug

三次作业中,在公测和互测中均未出现Bug。

第一次作业

编写代码的过程较为顺畅。测试时,针对三个加减号连续出现的情况进行了手动构造测试,程序能正确输出结果。回忆设计的时侯,正负号的归属问题(是归于表达式,还是归于项的开头,还是归于因子)有些细节问题。我针对这一点的处理是,将每项开头的不属于”项“的正负号寻找出来,单独处理。

当时写的注释:

// NOTE:
// BECAUSE if the first appeared number has [+-] in front of it,
// the [+-] will be belonged to this number
// so classified discussion will be difficult
// Instead, search for the first match, find the first capture's index
// then we could know how many [+-] should be handled independently.

第二次作业

在完成代码编写后进行了简单测试,发现了一个输出的细节问题。sin内部如果有表达式,则要输出形如sin((Poly1))这样的双重括号。出现了这个编码时的小疏忽。

后来在同学们的交流中,我意识到可以将自己的输出重新输入回去,利用自己程序的WF判断功能来检测输出合法性

第三次作业

由于第二次作业的架构设计是十分合适的,在增量开发中仅需修改数十行即可完成功能添加,而这对于程序正确性的影响很小。

第三次作业新增了WrongFormat格式判断,我在第二次作业中就已经构建了WF异常类,并在开发中随手进行的部分的判断,但是不全面。在第三次作业中,正式审视递归下降过程,在相应需要特判的地方进行异常的抛出,将WF判断功能完善。在测试中手动构造各种错误格式,包括空串,甚至unicode空字符都尝试过,程序都能够正确输出。最终,程序通过了强测和互测。

分析自己发现别人程序Bug所采用的策略

本人仅在第一次作业中发现了他人的Bug,第一次作业用了自己编写的简单评测脚本。利用项的正则表达式,使用xeger生成随机数据。互测屋中,有人出现了大数的bug,问题出在正则表达式中,对某个字符进行+的匹配错误写成了{1,100}。

第二次作业随机生成测试数据并未找出他人bug。在互测公布后,发现屋内有人的程序在特定情况下会出现无输出的情况,这是由于部分细节处出现0而未正确处理。

第三次作业未找出bug。

重构经历总结

可以说,第一次作业和二三次作业完全是两个不同的项目。两次的设计于构造都是完全独立的。

心得体会

这一单元作业,能够体现面向对象思想的主要在第二三次作业,对整体架构的思考及设计在整个项目进行中处于最前期,且花了一定的时间。而从开发过程以及最终的结果来看,前期的准备与设计是必要的也是十分重要的。在这个时期要充分思考各种问题,并预测需要用到的技术/工具,进行简单的搜索及预习。针对项目的需求,进行信息的提取,进行抽象,把他们转换为变成语言中的”类“。有良好的对整体的审视,便有利于写出适合的implement关系,extends关系。拿二三次作业为例,Factor抽象类中的三个方法,就是不同元素的行为共性。

本人在性能优化上并没有多么深入。从开始设计时就把程序的正确性以及健壮性放在首位,所以只把基础的合并优化以及树结构的优化纳入考虑范围内(事实证明树结构的优化还是很有必要的,比如(1+(2+sin(0)+3))就能够优化成6),将面向对象重点放在架构的设计和具体的实现上。另外,最终不错的评测分数也体现了,本项目中的优化也是可以满足要求的。

发表评论

0/200
80 点赞
0 评论
收藏
为你推荐 换一批