面向对象设计与构造第一次总结作业

第一次作业——多项式计算

---结构分析

  第一次作业我只使用了两个类,正像下面的类图所表示的那样,分别是Poly和ComputePoly。Poly类是不可变的,能保存一个多项式,可以进行加、减运算。ComputePoly是程序的主类,能够读取一个多项式加减运算表达式的字符串,并输出计算结果。parseExpression方法通过调用parsePoly方法和parseOperator方法将输入的字符串转换为Poly对象和运算符列表。compute方法从polyList和opList取出多项式和运算符进行计算,返回计算结果。

  总的代码量是248行(后面的度量分析图中可以看到),其中ComputePoly类占167行,Poly类81行;程序总共有28个方法,Poly占10个,而ComputePoly占18个。由于写Poly类的时候参考了教材的写法,Poly还算比较合适,无论是从两个类的代码量还是方法个数来看,ComputePoly都显得不太协调。事实上也如此,ComputePoly做了除计算外所有的事情:处理输入(包括错误处理,提取多项式),将多项式和运算符们存到数组里,调用compute方法得出结果,最后输出结果。这是典型的面向过程式的思路,颇有一点用C语言写程序的味道。

第一次作业类图

  再仔细一想,ComputePoly做这么多事情合适吗?就这个不那么复杂的程序而言,管理ComputePoly所做的事情还是能够接受的,但是再实现一个乘法功能呢?很明显,上面的两个类都需要做修改,对于Poly而言,只需增加一个计算乘法的方法就可以了,但是对ComputePoly来说,第1,4,5,6共4个方法都需要修改。假如按照建议设计那样将ComputePoly进一步分成3个类InputHandler,PolyManager,PolyArithmetic,同样地,输入处理InputHandler要做修改,PolyManager也需要修改,但PolyArithmetic不需要修改。实际该改的都得改,说白了还是前面所说的第1,4,5,6个方法。但是在这样一种设计下进行修改的复杂性就降低了,修改InputHandler时只需要记得正则表达式改一下能匹配乘号,而不需关心乘法和加法在一起时的谁先计算谁后计算的问题;而修改PolyManager时也无需关注输入是否处理好了,只用专心实现calculate方法和appendOperator方法(如下图)。这样一来不用在ComputePoly长长的代码中苦苦寻找某个方法,上改下改,减少出错的可能性,二来也不会被整体的复杂性所烦扰,分解成两个问题后可单独实现。

  无论如何,在初识面向对象设计前我还是带着面向过程式的思维,幸好能够从教材上学习到如何编写Poly类(非常感谢第一次作业时有这么好一个范例),初步领略了OO之美。下图是用eclipse的metrics插件对第一次作业代码分析得到的结果。重点关注一下第一个McCabe Cyclomatic Complexity,它中文名叫圈复杂度,是流程控制图中独立路径的数目,主要由分支和循环个数决定,越大表明越复杂,测试时所需考虑的情况也就越多(因为每个路径都要被测试),当它非常大的时候,程序的测试就变得十分复杂。这里的最大值是10,还能接受。仔细查看,造成最大值的方法是ComputePoly里的parsePoly方法,原因可能是需要进行输入检查,涉及的不同错误输入种类数较多。

   Nested Block Depth是if,while,for的嵌套深度。这里是最大值3,处于正常范围。最大值一个来自Poly的add方法,考虑到实现两个多项式加法的细节,是可以接受的,另一个同样来自parsePoly方法,原因同样是涉及输入错误处理。

  再看一下Method Lines of Code,方法的代码行数,这里最大值是26,不算太大。

第一次作业度量计算

---BUG分析

  此次作业的bug在于正则表达式的匹配,我使用一个正则表达式试着去匹配整个输入的字符串,潜在的危险是堆栈溢出。在分类树上是有“压力测试”这一项的,而且助教也说过注意不要爆栈,但是当时没想明白什么会导致栈溢出,而且是第一次使用正则表达式,主观上也认为压力测试没什么用,就没有构造相应的测试样例,导致了这个bug的产生。bug出现在isCorrectFormat方法中,这个方法检查了输入的字符串是否符合规定的格式。要解决这个问题可采用分段匹配的方式,整个字符串是多个多项式,中间用加减号连接,因此可以分别匹配每个多项式。

  测试同学代码时,我使用了每个分支树结点的对应测试用例(除了压力测试)。同学的代码中存在类似以“f”,“ff”,“fff”命名的变量,我尝试着去理解同学的意图,再加上大量的分支循环嵌套,实在是难以揣测。

第二次作业

---结构分析

  第二次作业一开始我花了一天的时间理解作业指导书,从头到尾读了好几遍才弄清楚。到第三次作业也是如此,我试着边读边用自己的语言去表达指导书的规定,并且举出例子,然后分条把觉得重要的点写在纸上,这样做有些笨拙和繁琐,不过的确能帮助我理解指导书的意图。

  课件中给出了提示,要构造电梯类、调度类、请求类、请求队列类和楼层类共5个类(如下图)。要怎样确定该哪个类该做什么,这是除了理解指导书外另一件头疼的事情。左思右想,苦思冥想,难以划分各个类的职责。另一个问题是调度器类的command和schedule方法,弄得我一头雾水,写完后都没能参透其中奥秘,直到看了互测同学活生生的代码,才恍然大悟。

 

第二次作业建议

  第二次作业的要点是如何把程序功能均衡地分配给各个类,如何让多个类之间协同工作,要避免出现Idiot Class和God Class。从下面的类图中可以看到Floor类就是比较白痴的一个类,它只知道楼层顶楼和底楼的编号。其实,可以考虑让楼层类知道更多的信息,比如某层楼是否有电梯到达。

  除了出现了一个Idiot Class外,另一个缺点是,在main方法里展开对输入的处理,这与第一次作业相同,由于自己没能意识到这种做法的坏处,在第二、三次作业时仍没有加以改正。

  本次作业的设计是否均衡呢?下面就再用定量的方法分析一下。 

 

第二次类图

  每个类的属性个数、方法个数、代码行数如下面的表格所示,其中方法个数包括了构造方法。从数据上看,方差较大。代码行数最多的类是ElevatorSystem,可能是因为在这个类里做了输入处理,如果把输入的处理分开来,应该会更均衡一些。

傻瓜电梯
 ElevatorElevatorSystem FloorRequest RequestQuue Scheduler 均值 方差 
属性个数 74 3.3 4.6
方法个数 14 2 4 9 10 6 7.5 19.1
类代码行数 95 107 18 48 38 55 60.21170.2 

    

  一个比较大的数据是电梯类的方法个数14,其中用于状态查询的方法占了一半。由于事先未规划好,在编码的时候为了方便,新增了一些方法。仔细分析会发现一些方法是冗余的,比如getStatus方法,事实上这个方法也从未被调用过。另一个原因可能是题目要求的电梯状态是定义在左开右闭区间上的,有时候为了方便我会使用左闭右开区间,这也增加了一定的复杂性。

  再看一下类的职责是否明确。

  拿电梯类举例,它总共有14个方法,方法总数占到整个程序约1/3,但仔细看,只发现能够让电梯改变状态的只有前两个方法readyToGotoFloor和run,run方法是让电梯运行到0.5s后的状态,而前者确定电梯的下一个目标。也就是说,别的类只能告诉电梯下一次去哪个楼层,电梯只管去,并且自己决定方向,其他类不能干涉电梯的运动方向。假设其他类能直接修改电梯的方向,那么在这个设计中,如果调度器让电梯向下走,但又是去往楼层数高的地方,这明显是不合适的。电梯内部不存在请求队列,无论何时,电梯都只有一个目标,它不用操心有多少请求在队列中等着它执行,只用听从调度类的指挥就可以了。

  从功能的角度上看,电梯的职责是明确而单一的。但是这样做的复杂性在于,电梯调度类需要精心地设计,在每次给电梯发送命令前,需要使用电梯类提供的一系列状态查询方法检查电梯状态(之所以要检查是因为调用readyToGoToFloor会立即改变电梯的运动状态,例如当电梯向上运行时,调用方法让电梯去往比当前楼层数小的楼层,运动方向就会突然改变)。因此,调度类必须充分了解电梯各个状态的含义(尽管它不需要了解电梯是怎样确定自己的状态的)和一些内部细节,否则就可能会导致电梯出故障。这就在一定程度上增加了电梯类与调度类的耦合性,一是使编码时复杂性增加,二来修改、新增功能时容易出错(例如我在写第三次作业的时候就在这上面犯了很多错误)。

   下图是第二次作业的度量分析结果。可以明显地看到标红的圈复杂度较高,最大值是17。进一步细看(图中未给出)可以发现高复杂度的来源主要是ElevatorSystem类的parseRequest方法和main方法,以及Elevator类的run方法。前者是由于输入的错误情况较多,个人写得比较凌乱,判断逻辑复杂,main方法里也做了输入的处理。后者是电梯运行时的逻辑稍微复杂,分支较多,也有两层嵌套的情况。

  再看一下每个方法的行数(图中最后一行),最大值是46,与第一次作业相比有所增加,其来源同样是parseRequest和main方法。如果将输入处理部分单独封装在一个类中,并且优化一下错误处理的逻辑,应该能使整体设计均衡一些。

第二次作业度量分析图

---BUG分析

  这次作业的bug是在时间很大时运行的时间较长,需要十几秒,这是由于实现采用了每0.5s进行一次操作的方式。

第三次作业

---结构分析

  本次作业在前一次作业的基础上增加了捎带功能,用继承的方式实现了ALSScheduler,对其他的类也做了一些调整。

 

第三次类图

  由于保留了第二次作业的大部分内容,本次作业在均衡性上没有改进,反而由于增加捎带功能后变差。尤其是ALSScheduler,代码行数最多,逻辑也较复杂。

稍微聪明一点的电梯
 ElevatorElevatorSystem FloorRequest RequestQuue Scheduler ALSScheduler均值 方差 
属性个数 8434 3.9 4.1
方法个数 17 2 4 12 11 512 9 29.3
类代码行数 108 108 18 59 48 55141 76.71857.9

  细心的读者可能会发现这次的圈复杂度下降了1,但这不是因为进行了优化,只是做了点微调。这里最大值16也不是前面提到的输入处理带来的,而是来自ALSScheduler的command方法。为了实现捎带功能,我增加了很多条件判断,既难写,又难以理解和修改。

第三次度量

---BUG分析

  1. 第一条请求没能够支持前导零和正号。在第二次作业中是没有这个bug的,但这次对第一条请求有了更特殊的要求,要求只能(FR,1,UP,0),我只是用字符串是否相同的方式判断了一下,没能够考虑到特殊中的普遍性。
  2. 一次开门完成了多条请求,没能按请求发出时间顺序输出。
  3. 没能实现“一个请求完成后,其附带的顺路捎带请求可能未完成,此时按照时间顺序,将未完成的最先顺路捎带请求升级为主请求”。由于我没能发现不按“时间顺序”与“按时间顺序”有什么区别,经过多次思考后没有想清楚,主观臆测,存在侥幸心理,就导致了这个bug的产生。

  上面的3点都是给我互测的同学发现的,这里要感谢这位同学。

  最后再分析一下第2、3个bug与设计结构的关系。这两个bug都位于ALSScheduler类中,具体在多个方法中都有体现,究其原因,是使用了Java标准类库中的优先队列。这个队列专用于捎带队列,我按照到达楼层的时间(先后)作为各个请求的优先级,能够最早达到的,排在队首,晚到的,排在后面。问题出在同一时刻进队的请求,在出队时可能失去了输入时的顺序以及请求发出的时间顺序,这就导致了第2个bug的产生。另一方面,当主请求执行结束时,处在捎带队列队首的请求未必是按照请求发出时间最早的。

  我能想到的解决办法是专门实现捎带请求队列类,兼顾到达时间顺序与请求时间顺序。在下一次作业中我会尝试着改正。

 

转载于:https://www.cnblogs.com/eggert/p/8688046.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/369155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python字符串常见操作

字符串常见操作 如有字符串mystr hello world itcast and itcastcpp&#xff0c;以下是常见的操作 <1>find 检测 str 是否包含在 mystr中&#xff0c;如果是返回开始的索引值&#xff0c;否则返回-1 mystr.find(str, start0, endlen(mystr))<2>index 跟find()方法…

调整图像的灰度级数C++实现

图像灰度级数我们见得最多的就是256了&#xff0c;如果想调整它的灰度级数&#xff0c;我们可以使用图像库的imadjust函数来作出调整&#xff0c;比如讲256个灰度级变成2个灰度级&#xff08;也就是二值图了&#xff09;。再举一个例子&#xff0c;原来一幅256个灰度级的图像&a…

[BZOJ2095][Poi2010]Bridges 最大流(混合图欧拉回路)

2095: [Poi2010]Bridges Time Limit: 10 Sec Memory Limit: 259 MBDescription YYD为了减肥&#xff0c;他来到了瘦海&#xff0c;这是一个巨大的海&#xff0c;海中有n个小岛&#xff0c;小岛之间有m座桥连接&#xff0c;两个小岛之间不会有两座桥&#xff0c;并且从一个小岛…

excel和python建模_利用Excel学习Python:准备篇

写在前面这个系列我们要利用Excel的知识&#xff0c;学会用python进行数据分析&#xff0c;如果你精通Excel想要用python提高数据分析效率&#xff0c;那么这个系列你来对了&#xff0c;如果你已经是python大神&#xff0c;想要建模/算法等高级技巧的&#xff0c;这个系列可能不…

方法内联在JVM中有多积极?

IntelliJ IDEA中使用Ctrl Alt M 提取方法 。 Ctrl Alt M。 这就像选择一段代码并按此组合一样简单。 Eclipse也有它 。 我讨厌冗长的方法。 对于我来说&#xff0c;闻起来太久了&#xff1a; public void processOnEndOfDay(Contract c) {if (DateUtils.addDays(c.getCrea…

Python正则表达式基础

1. 正则表达式基础 1.1. 简单介绍 正则表达式并不是Python的一部分。正则表达式是用于处理字符串的强大工具&#xff0c;拥有自己独特的语法以及一个独立的处理引擎&#xff0c;效率上可能不如str自带的方法&#xff0c;但功能十分强大。得益于这一点&#xff0c;在提供了正则表…

Java并发:隐藏线程死锁

大多数Java程序员熟悉Java线程死锁概念。 它本质上涉及2个线程&#xff0c;它们彼此永远等待。 这种情况通常是平面&#xff08;同步&#xff09;或ReentrantLock&#xff08;读或写&#xff09;锁排序问题的结果。 Found one Java-level deadlock:"pool-1-thread-2"…

vue中使用axios发送请求

我们知道&#xff0c;vue2.0以后&#xff0c;vue就不再对vue-resource进行更新&#xff0c;而是推荐axios&#xff0c;而大型项目都会使用 Vuex 来管理数据&#xff0c;所以这篇博客将结合两者来发送请求 1.安装axios cnpm i axios -S 2.方案一&#xff1a;修改原型链 首先&…

django缓存

由于Django是动态网站&#xff0c;所有每次请求均会去数据进行相应的操作&#xff0c;当程序访问量大时&#xff0c;耗时必然会更加明显&#xff0c;最简单解决方式是使用&#xff1a;缓存&#xff0c;缓存将一个某个views的返回值保存至内存或者memcache中&#xff0c;5分钟内…

linux 输入法成繁体字_寻找Ubuntu中繁体字输入法

当客户来自港台地区时&#xff0c;英文和繁体字就成了交流的主要工具。windows下我们有搜狗输入法可以切换简体与繁体&#xff0c;那么Ubuntu下怎么办&#xff1f;這是我第一次考慮這個問題&#xff0c;在我的印象裏Linux下的中文輸入法還不是那麼完善&#xff0c;所以我進行了…

vue跨域解决方法

vue跨域解决方法 vue项目中&#xff0c;前端与后台进行数据请求或者提交的时候&#xff0c;如果后台没有设置跨域&#xff0c;前端本地调试代码的时候就会报“No Access-Control-Allow-Origin header is present on the requested resource.” 这种跨域错误。 要想本地正常的调…

CSS盒子模型之详解

前言&#xff1a; 盒子模型是css中最核心的基础知识&#xff0c;理解了这个重要的概念才能更好的排版&#xff0c;进行页面布局。一、css盒子模型概念 CSS盒子模型 又称框模型 (Box Model) &#xff0c;包含了元素内容&#xff08;content&#xff09;、内边距&#…

LeetCode的二分查找的练习部分总结

这两天由于工作的原因&#xff0c;一直没有写博客&#xff0c;但是却把LeetCode上面的题目做了不少——二分查找。上面这些题都是这两天写的。现在简单做一个总结。 首先二分查找的思想就是对一个有规律的元素&#xff08;事情&#xff09;进行不断的排除&#xff0c;最后找到符…

在Mac上安装IntelliJ IDEA

在Mac上安装IntelliJ IDEA http://www.jetbrains.com/idea/documentation/ 入门视频 这篇文章旨在介绍如何在Mac系统上安装IntelliJ IDEA&#xff0c;至于IntelliJ IDEA的介绍和使用方法&#xff0c;大家另行查阅&#xff0c;本篇的文章不再详细阐述。 简短解说&#xff0c;I…

行内元素,块级元素,各自特点及其相互转化

作为一名小前端&#xff0c;块级元素、行内元素用了几千几万次&#xff0c;除了“块级独占一行&#xff0c;行内不独占”之外&#xff0c;对细节属性的了解十分匮乏&#xff0c;今天做以部分属性的测试和阐述。 一、 对物理属性的支持 元素类别widthheightpaddingmargin是否独…

从RSS Feed和YQL创建数据表

Yahoo Query Language&#xff08; YQL &#xff09;是一种查询语言&#xff0c;例如SQL。 使用YQL&#xff0c;我们可以跨Web服务 查询 &#xff0c; 过滤和联接数据。 YQL也可以阅读RSS feed。 响应可以是JSON或XML。 雅虎提供了一个YQL控制台&#xff0c;用于调试 &…

Qt之QSS(Q_PROPERTY-自定义属性)

版权声明&#xff1a;进步始于交流&#xff0c;收获源于分享&#xff01;纯正开源之美&#xff0c;有趣、好玩、靠谱。。。作者&#xff1a;一去丶二三里 博客地址&#xff1a;http://blog.csdn.net/liang19890820 目录(?)[] 简述 在Qt之QSS&#xff08;Q_PROPERTY-原始属性&a…

python print error 空_python笔记37:10分钟掌握异常处理,再也不担心程序挂了

主要内容&#xff1a;小目标&#xff1a;异常处理主要内容&#xff1a;错误与异常&#xff0c;try_except语句对于撸代码的程序员来说&#xff0c;程序运行中出现问题是常见的现象&#xff1b;实际学习与工作中&#xff0c;我们会花很大的精力去解决各种问题&#xff1b;1. 程序…

contenteditable元素的placeholder输入提示语设置

在某些情况下&#xff0c;textarea是不够用的&#xff0c;我们还需要显示一些图标或者高亮元素&#xff0c;这就需要用富文本编辑器&#xff0c;而富文本编辑器本质上是HTML元素设置了contenteditable。 然后可能需要像input、textarea有placeholder的输入提示语&#xff0c;但…

css 浮动和清除浮动

在写页面布局的过程中&#xff0c;浮动是大家经常用的属性。在好多的排版布局中都是用的的浮动比如说下面这些地方都是应用到了浮动。 在我学习浮动的时候可是熬坏了脑筋&#xff0c;在这里我分享一下我对浮动这块知识的总结。 一、浮动的定义 使元素脱离文档流&#xff0c;按…