OO第一次单元总结
第一次总结性博客
16071070 陈泽寅 2019.3.23
一、第一单元所学总结
- 首先先来总结一下第一单元我所学到的知识以及所感所悟。第一个单元,是我第一次接触JAVA语言,并且再使用了几次之后,就被这门语言的独有的魅力以及简便的用法所深深吸引。下面我从三个方面来简单阐述一下我对于JAVA相比较于c语言的优势。
- (1)从架构上来说,java的设计思路是不同于c的,它是一门面向对象的语言,我们的思维从熟悉的过程式编程语言转移到了对象思维上来。这样的思维的好处是,我们可以将一个大的问题分成很多个小的类去进行处理。如果说过程式编程是一个庞大的整体,而函数是其一个个功能的分布,那么在java里类就是实现各个子模块的实现者。在java的面相编程思维中,类的设计秉持高内聚,低耦合的思想。即在每个类的内部只关心自己类的操作,而不去关心其他类的事情。这样的好处是,我们把整个过程细化成很多个类去实现,每个类只需实现自己的功能,而不需关心其他类的功能。这样方便程序员在写每个模块的时候不需考虑当前类对其他类的影响,并且方便进行单元测试以及问题的发现。同时当某个需求发生改变时,只需更改相应的类,而不需去修改其他相关的类。因为类与类之间的关系是低耦合的。这样方便日后的维护与调试。
- (2)从设计安全性的角度来说,java在大型项目开发的时候更加安全。因为每个类的属性都是private的,其他类不能直接访问当前类的私有属性,因此无法直接对属性的值进行修改。这是安全的,因为其他类可能并不知道这个类的设计原则,若直接修改类的属性可能导致bug的产生。java针对这种情况提供了public方法,其他类可以调用public方法去实现类属性的修改,而一些修改的限制都写在方法中,因此其他类无需知道这些细节,并且这些public方法也保证了类属性数据的安全性。同时java还提供了接口的思想,即很多不同的类为了实现某个接口,就能实现类与类之间的联系。这样就大大增加了程序的可扩展性和可移植性。
- (3)java还有一个很大的优势就是其写法相当简便,相比于c它提供了大量的内置函数包以供调用,比如其String类,ArrayList类,HashMap类等等。还有一些sort,find函数,这些函数都是经过优化的方法,省去了程序员一些复杂地基本操作,使程序可读性增强。
二、多项式求导程序设计说明
(1)第一次作业:
第一次作业的设计,现在看来真是杂乱不堪,完全是面相过程式编程,只不过是套了一个JAVA的壳子而已,存在很多的问题。
第一次作业的题目是求一个简单多项式的导数。所有的表达式都是形如\(f(x) = \Sigma a*x^c\)这种形式。这个多项式是一个字符串类型,当然我们首先应该判断其是否合法。我的思路是首先通过正则匹配检查是否存在不合法的空格。
String pattern1 = ".[+-]\s[+-]\s+\d.";
String pattern2= ".[\^]\s[+-]\s+\d.";
String pattern3 = ".\d+\s+\d+.";
```
上述的三个表达式分别判断了是否存在表达式加法时的带符号整数之间的非法空格,数字与数字之间的非法空格,以及指数后的带符号整数的非法空格。在判断不存在这样的非法空格之后,为了方便后续的项的分离,我们将字符串中的所有空格和\t制表符全都删去。
然后我们对整个字符串进行合法性的检验,检查其每一项是否都属于\(f(x) = a*x^c\)这种形式。这里的正则表达式如下。
pattern1 = "([+-]{0,2}(((\\d+[*])?[x]([\\^]([+-])?\\d+)?)|(\\d+)))";
我们这里需要注意,因为其是贪婪性匹配,每次都去匹配满足上述模式的最大字符串,因此当字符串巨大时可能会存在爆栈的情况,因此我们调用Pattern Matcher里的方法将其分成一个个小的GROUP,并得到每个表达式的系数以及指数,并将其存在多项式类中
下面是第一次作业的类图
第一次作业的失败主要是把所有的函数方法都写在了一个类里面,无论是数据的读取,还是表达式的构造,以及最后的输出,都是在一个Main类里,导致这个类的长度达到了几百行。这是明显违反了OO的设计准则的。正确的设计方法应该是首先构造一个读取类,这个类专门用来读取数据,并且存储我们的字符串。然后再写一个判断类,来判断我们的字符串是否合法。再通过一个分离类,将我们的字符串进行分离,将分离出来的数据存储在我们的多项式类中,最后再通过输出类来进行数据的输出。这样每个模块功能明确,并且当日后增加需求的时候,大的模块不需要变动,只需在各个类中添加或者修改方法即可。
(2)第二次作业:第二次作业在第一次作业的基础上增加了\(sin(x)以及cos(x)这两种幂函数\),并且在每个表达式中允许存在两个因式相乘的形式。由于第一次作业的偷懒设计,导致第二次作业的架构需要重新设计,但是在做完第三次作业之后发现第二次作业的设计依旧是有很大的弊端。
第二次作业的多项式的形式是 \(f(x)=\Sigma a*sin(x)^b*cos(x)^c*x^d\)。项里可以存在乘法,这就需要更改之前的表达式的分离的方法。首先我先更改了多项式的存储结构,运来的多项式里只包含x的指数和系数。现在加入了\(sin(x)和cos(x)\)的指数。最后得到一个项的list,并且根据相应的公式进行求导。求导的公式是
\(a*l*x ^ (a - 1)*cos(x)^c*sin(x)^b + b*l*x^a*cos(x)*cos(x)^c* sin(x)^(b - 1) - c*l*x^a*cos(x)^(c - 1)*sin(x)*sin(x)^b\)这次作业在上一次的基础上更新正则表达式的匹配样例
String patternHead = "[+-]{0,2}(([+-]?\\d+)|(" +
"[x]([\\^][+-]?\\d+)?)" +
"|([s][i][n][\\(][x][\\)]([\\^][+-]?\\d+)?)" +
"|([c][o][s][\\(]" +
"[x][\\)]([\\^][+-]?\\d+)?))([*](([+-]?\\d+)|([x]([\\^][+-]" +
"?\\d+)?)|" +
"([s][i][n][\\(][x][\\)]([\\^][+-]?\\d+)?)|([c][o][s][\\(]" +
"[x][\\)]([\\^][+-]?\\d+)?)))*";
通过这个表达式去得到一个个项,然后通过split
函数将\(*\)号分开得到一个个因式,再通过因式的匹配样例Pattern pattern = Pattern.compile("[+-]{0,3}[0-9]+");
Pattern pattern = Pattern.compile("([+-]{0,2})[x]([\\^]" +"?(([+-]?)(\\d+)))?");
Pattern pattern = "([+-]{0,2})sin[\\(]x[\\)]([\\^]([+-]?\\d+))?";
Pattern pattern = "([+-]{0,2})[c][o][s][\\(]x[\\)]([\\^]([+-]?\\d+))?";
分别得到了项的系数,\(x的指数,sin(x)的指数,cos(x)的指数\),然后存入我们的结构体中。最后通过上述求导公式对每个项进行求导,并且将相同系数的项合并。
类图如下
可以看到其实第二次设计依旧没有秉持好的设计原则,虽然将不同的功能写在不同的方法里,但是没有实现类的分类,这里好的设计应该是sin(x),cos(x),x单独分类,然后进行求导,以及输出。然而这里混在了一起,导致main方法十分庞大,修改一个地方会导致很多方法都需要修改。因为代码之间的耦合度非常高,并且几乎所有的操作都是写在Poly里的静态方法,导致第三次作业又需要进行大规模的重构。
(3)第三次作业
这次作业是我三次里认为还比较满意的一次作业,因为这次的题目比较复杂,因此我觉得不能再像前两次作业那样急于编写代码,因为这样会导致代码紊乱,最后难以找bug。因此在动手写代码之前,我仔仔细细地参照面相对象编程的思想,对第三次的题目进行了思考,先把类和接口设计好再进行动手编写代码,果然想清思路之后再下手,写起来快并且最后的bug也少了,代码思路非常清晰。简单分析一下这次的作业,这次的多项式求导不仅延续了之前的项的思路,还添加了嵌套求导的规则。即
表达式 = 项{+项}
项 = 因子*{因子}
因子 = (表达式)|因子
也就是类似\(sin(cos(x))\)这种嵌套的求导。我知道再延续以往的面相过程求导肯定是行不通的了。因此这次的设计对每一步进行了细致的类的划分。类图如下。
下面来讲一下我的做法,首先我这次划分了很多个类,常数类、x项类、cos项类、sin项类、指数项类、加法类、乘法类这些类,这些类都实现了一个求导的接口,也就是说所有求导的对象都是另一个可求导的对象,比如说指数类里,指数的底数也是一个实现了求导的类,这样就很好地体现了分类的思想,并且在指数这个类里,我只需管指数函数是如何求导的,而不需要管其底数是什么,因为底数实现了求导接口,底数也会自动去调其覆写的求导方法去对底数进行求导。这样就使我们的程序显得很简单。
这里的加法和乘法类就是指两个实现了求导接口的对象相乘进行求导,我们只需关心乘法的求导是怎么样的,而具体对象的求导,放在具体的对象的求导里去完成,这样就真正实现了低耦合的思想。具体的接口的代码如下:
public interface Derive {
public abstract Derive derive();//求导函数
public abstract void print();
}
然后乘法里实现的求导接口的求导方法如下。
```
public Derive derive() {
ArrayList<Derive> addList = new ArrayList<Derive>();
for (int i = 0; i < this.list.size(); i++) {
/** 根据几个函数连乘的求导法则进行求导
* result = (ui' * (u1*u2*....un)/ui)的和
*/
ArrayList<Derive> multList = new ArrayList<Derive>();
for (int j = 0; j < this.list.size(); j++) {
if (i != j) {
multList.add(this.list.get(j));
}
}
multList.add(list.get(i).derive());
Mult mult = new Mult(multList);
/**
* 这条multList 就是Add链中其中的一条
*
*/
addList.add(mult);
}
/**
* 至次为止, addList是由好几个Mult类构成
*/
return new Add(addList);
}
我们可以看到,对于$f(x) = h(x)*g(x)$的求导,只需关心$f(x)'=f(x)'g(x)+f(x)g(x)'$即可,而不需关心$f(x)'和g(x)'$是什么,因为$f(x)和g(x)$都是已经实现了求导方法的对象,在他的类里会调用自己的求导方法进行递归求导。
在明确了类的框架结构以后,我们再想办法对字符串进行处理,我一开始尝试原来的正则文法匹配的方法,但是发现自己不明白如何去产生上述的表达式里嵌套因子的方式,但是我发现,这个形式和我们之前学过的编译原理的递归下降分析法类似。于是我采用相同的思想先写了一个简单的词法分析小程序,然后构造了expr(),term(),factor()三个子程序来对字符串进行读取。这样就能实现程序的因子里嵌套表达式的形式了。下面举一个简单的表达式的例子。
```
/**
* 自顶向下的表达式子程序
*/
public static Derive expr() {
/**
* 表达式 = 项 {+项}
*/
ArrayList<Derive> exprList = new ArrayList<Derive>();
Derive term1 = term();
exprList.add(term1);
if (!checkExprTail()) {
System.out.println("WRONG FORMAT!");
System.exit(0);
}
while (sym.equals("Add") || sym.equals("Minus")) {
headFlag = 1;
term1 = term();
exprList.add(term1);
}
if (!checkTail()) {
err();
}
return new Add(exprList);
}
```
这样在后续的调试过程中我可以单独根据每种形式的求导来找问题,就能很快地发现是哪个求导过程发生了问题,简明扼要。
三:程序结构分析
各类代码总规模:(statistic)
ourceFile Total LinesSource Code Source Code Comment Lines Blank Lines erive.java 8 4 3 1 .java 10 8 0 2 onstant.java 25 19 0 6 inx.java 28 18 4 6 osx.java 28 20 3 5 ult.java 52 30 13 9 dd.java 54 40 3 11 egree.java 61 36 13 12 oly.java 390 345 21 24 otal 658 522 60 76 类的属性方法个数
属性 方法 erive.java 0 .java 0 onstant.java 1 inx.java 1 osx.java 1 ult.java 1 dd.java 1 egree.java 2 oly.java 8
四:程序优缺点
优点:
1:将复杂的多项式求导分成诸多形式的类,每个类只需注意自己的求导形式,具体的求导规则由各个实现求导接口的类去完成。
2:采用递归下降子程序法,使字符串的处理比较容易理解,并且不容易出错。
3:使用了接口的思想,方便可扩展性。
4:实现了高内聚低耦合的思想,使每个类和方法尽量能干的事情少,各自之间互不影响。
缺点:
1:在处理项的连乘的时候可能会出现爆栈的情况。
2:没有做完备的可能发生异常的情况的统计与测试。
3:单元测试不够完备,Main类的设计还过于冗杂。
4:存在大量的if-else语句,不够精炼,存在代码复用比较严重。
5:输出的时候如果嵌套层次太多,会导致大量的()产生,很难进行优化。
五:程序的Bug分析
第一次
第一次的bug在于没有处理指数或者系数过长可能抛出的异常,没有意识到助教和老师在作业指导书里的提示,这个问题在我理解了BigInteger类之后得到了较好的解决。并且在checkStyle风格检查的时候,没有按照规范要求的行数以及缩进。还存在表达式第一项如果为数字的话前面的符号的个数的问题。
第二次
- 第二次的第一个小bug是未考虑Scanner异常输入时抛出的异常会使程序crash,这里只需要在输入的地方加上try-catch的异常处理即可。
- 第二个bug是在去除空格的时候没有吧制表符\t一起去除,没有重视空格space与制表符在ASCII码上不同的本质区别。
- 第三个bug是在输出的时候,我是按照不管指数系数为不为0或1,全部将其按照\(a*x^b*sin(x)^c*cos(x)^d\)的格式输出,然后再对字符串进行处理,去掉"+1","1","^1"这些,但是忽略了完备性,如果一个指数恰好是 \(y^12\),那么去掉\(*1*\)之后就变成了\(y2\),这明显是错误的。错误的原因就是没有对类进行细分,如果按照第三次作业的方式对每个函数进行分类输出就会简单很多,可以分别判断系数、指数为0为1的情况,就可以省去大量的if-else并且保证程序的正确性。
第三次作业
- 没有考虑到输出的时候\(sin(2*x)\)这种错误的输出格式带来的问题。
- 一开始没有做左右括号匹配的处理。
六:自我评价与寄语
通过第一个单元的学习,已经基本掌握了各种java类的用法,也理解了面向对象的设计思想。了解了继承与接口的原理。但是在使用上还存在不熟练的时候。希望在日后进行多线程学习之前,能够把java的基础打扎实,写出漂亮稳定的好程序。
OO第一次单元总结的更多相关文章
- OO第一次博客作业--第一单元总结
OO第一单元总结 面向对象设计与构造的第一单元,对“面向对象”的概念还根本不理解不熟悉,只觉得需要“分模块”,但不知道怎么分,分多少模块,怎么根据需要的模块的功能建立类.学习的进度又太慢,根本跟不上出 ...
- OO第一单元作业总结
oo第一单元的作业是对多项式的求导.下面就是对三次作业分别进行分析. 第一次作业 分析 第一次作业相对来讲比较简单,甚至不用面向对象的思想都能十分轻松的完成(实际上自己就没有使用),包含的内容只有常数 ...
- OO第一单元总结
OO第一单元作业总结 一.前言 开学四周,不知不觉已经做了三次OO作业.事实上,每一次作业对我来说都是很大的挑战,需要花费大量的时间和精力来学习. 虽然学得很艰苦,但最后还是连滚带爬地完成了.(好惨一 ...
- oo第二单元作业总结
oo第二单元博客总结 在第一单元求导结束后,迎来了第二单元的多线程电梯的问题,在本单元前两次作业中个人主要应用两个线程,采用“生产者-消费者”模式和共享数据变量的方式解决问题.在第三次作业中加入多个电 ...
- OO第一单元优化博客
OO第一单元优化博客 第一次作业: 合并同类项+提正系数项+优化系数指数0/1=满分 第二次作业: 初始想法 一开始是想以\(sin(x)\)和\(cos(x)\)的指数作为坐标,在图上画出来就可 ...
- 【OO学习】OO第二单元作业总结
OO第二单元作业总结 在第二单元作业中,我们通过多线程的手段实现了电梯调度,前两次作业是单电梯调度,第三次作业是多电梯调度.这个单元中的性能分要求是完成所有请求的时间最短,因此在简单实现电梯调度的基础 ...
- 【OO学习】OO第一单元作业总结
OO第一单元作业总结 在第一单元作业中,我们只做了一件事情:求导,对多项式求导,对带三角函数的表达式求导,对有括号嵌套的表达式求导.作业难度依次递增,让我们熟悉面向对象编程方法,开始从面向过程向面向对 ...
- OO第二单元(电梯)单元总结
OO第一单元(求导)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉理解和掌握多线程的思想和方法.这个单元以电梯为主题,从一开始的最简单的单部傻瓜调度(FAFS)电梯到最后的多部 ...
- OO第一单元(求导)单元总结
OO第一单元(求导)单元总结 这是我们oo课程的第一个单元,也是意在让我们接触了解掌握oo思想的一个单元,这个单元的作业以求导为主题,从一开始的加减多项式求导再到最后的嵌套多项式求导,难度逐渐提高,编 ...
随机推荐
- 带你精读你不知道的Javasript(上)(一)
斌果在这几天看了下你不知道的js这本书,这本书讲的东西还是挺不错的,其中有很多平时我压根没接触到的概念和方法.借此也可以丰富一下我对js的了解. 第一部分 第一章 作用域是什么? 1.程序中一点源代码 ...
- Tensorflow之基于LSTM神经网络写唐诗
最近看了不少关于写诗的博客,在前人的基础上做了一些小的改动,因比较喜欢一次输入很长的开头句,所以让机器人输出压缩为一个开头字生成两个诗句,写五言和七言诗,当然如果你想写更长的诗句是可以继续改动的. 在 ...
- Springboot 系列(七)Spring Boot web 开发之异常错误处理机制剖析
前言 相信大家在刚开始体验 Springboot 的时候一定会经常碰到这个页面,也就是访问一个不存在的页面的默认返回页面. 如果是其他客户端请求,如接口测试工具,会默认返回JSON数据. { &quo ...
- pytorch深度学习60分钟闪电战
https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html 官方推荐的一篇教程 Tensors #Construct a ...
- go并发调度原理学习
aaarticlea/jpeg;base64,/9j/4AAQSkZJRgABAQAAkACQAAD/4QB0RXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAA
- 关于Exceptionless的使用注意
大家都应该比较熟悉NLOG,我们知道log4net和nlog,也有其它的记日志框架.目前我们的生产环境使用nlog,而且对Exceptionless的对接也是无缝的.可能有人会问为什么不用ELK,主要 ...
- 【问题】VS问题集合,不用也要收藏防止以后使用找不到
在日常的使用或者工作当中我们的vs会时不时的给我一些小“惊喜”.让我们有时候无可奈何.这不今天我又遇到了所以我决定记录下这些,方便以后再次出现好解决. 无法启动iis express web 服务器 ...
- odoo 12企业版与免费社区版的区别,价格策略与技术支持指南的全面解析
Odoo / Ps Cloud收费企业版是对社区版的极大增强,除了增加了很多功能外,最大的功能区别是企业版支持条码而社区版不支持,企业版对手机支持更好.有单独的APP,最重要区别的是企业版提供底层技术 ...
- Java递归方法遍历二叉树的代码
将内容过程中经常用的内容做个记录,如下内容内容是关于Java递归方法遍历二叉树的内容. package com.wzs; public class TestBinaryTree { public st ...
- springCloud feign使用/优化总结
基于springCloud Dalston.SR3版本 1.当接口参数是多个的时候 需要指定@RequestParam 中的value来明确一下. /** * 用户互扫 * @param uid 被扫 ...