20145208 实验二 Java面向对象程序设计
实验内容
- 初步掌握单元测试和TDD
- 理解并掌握面向对象三要素:封装、继承、多态
- 初步掌握UML建模
- 熟悉S.O.L.I.D原则
- 了解设计模式
实验步骤
(一)单元测试
三种代码
- 伪代码
- 产品代码
- 测试代码
- 在这里我通过一个复数计算的例子来验证这三种代码。
代码需求
- 实现复数的加法、减法和乘法计算,并输出结果的功能。
伪代码
- 根据上面的需求,可以先写出程序的伪代码,伪代码是一个程序最好的注释,有助于我们理解问题和解决问题,改程序伪代码如下:
设定两个复数并计算:
两个复数相加
两个复数相减
两个复数相乘
正确格式输出
产品代码
- 根据伪代码不难编出产品代码,通过老师给的关键方法提示,我定义了设定复数的方法,代码段如下:
public ComplexOf20145208(double m_dRealPart,double m_dImaginPart){
this.m_dImaginPart=m_dImaginPart;
this.m_dRealPart=m_dRealPart;
}
- 然后根据伪代码的流程来定义计算的三种方法,比如加法,代码如下:
public ComplexOf20145208 Add(ComplexOf20145208 c){
double realPartSum=m_dRealPart+c.m_dRealPart;
double imaginPartSum=m_dImaginPart+c.m_dImaginPart;
return new ComplexOf20145208(realPartSum,imaginPartSum);
}
- 其他的几种方法都与之类似,不作描述。
- 在输出的部分需要特别注意一下输出的格式,因为复数部分的符号和有无都有不同情况,所以这我需要在方法中设定几种情况,即复数的系数为整数、0或负数的时候,当然,为了符合全面性,还要包括不属于以上的输出情况,代码如下:
public String toString(){
if (m_dImaginPart>0){
return this.m_dRealPart+"+"+this.m_dImaginPart+"i";
}
else if (m_dImaginPart<0){
return this.m_dRealPart+this.m_dImaginPart+"i";
}
else if (m_dImaginPart==0){
return this.m_dRealPart+"";
}
return "Wrong!";
}
}
- 到此为止,代码基本上实现了伪代码所描述的功能,可以整理在一起构成一个完整的产品代码,代码如下:
public class ComplexOf20145208 {
private double m_dRealPart;
private double m_dImaginPart;
public ComplexOf20145208(double m_dRealPart,double m_dImaginPart){
this.m_dImaginPart=m_dImaginPart;
this.m_dRealPart=m_dRealPart;
}
public ComplexOf20145208 Add(ComplexOf20145208 c){
double realPartSum=m_dRealPart+c.m_dRealPart;
double imaginPartSum=m_dImaginPart+c.m_dImaginPart;
return new ComplexOf20145208(realPartSum,imaginPartSum);
}
public ComplexOf20145208 Minus(ComplexOf20145208 c){
double realPartMin=m_dRealPart-c.m_dRealPart;
double imaginPartMin=m_dImaginPart-c.m_dImaginPart;
return new ComplexOf20145208(realPartMin,imaginPartMin);
}
public ComplexOf20145208 Multi(ComplexOf20145208 c){
double realPartMul=m_dRealPart*c.m_dRealPart;
double imaginPartMul=m_dImaginPart*c.m_dImaginPart;
return new ComplexOf20145208(realPartMul,imaginPartMul);
}
public String toString(){
if (m_dImaginPart>0){
return this.m_dRealPart+"+"+this.m_dImaginPart+"i";
}
else if (m_dImaginPart<0){
return this.m_dRealPart+this.m_dImaginPart+"i";
}
else if (m_dImaginPart==0){
return this.m_dRealPart+"";
}
return "Wrong!";
}
}
测试代码
- 在IDEA中建立测试代码很方便,只需要在要进行测试的代码中右键然后在选项中选择GO TO……Test就可以建立一个测试类:
- 还可以在这里直接选择要测试的内容,IDEA会帮助自动建立相关模块,然后我们就在模块里面输入我们的测试内容了。
- 对于测试代码,要尽量的全面,首先可以测试常规情况,比如对于复数的计算,就可以先进行如下测试:
@Test
public void testComplex()throws Exception{
assertEquals("7.0+9.0i",new ComplexOf20145208(2,6).Add(new ComplexOf20145208(5,3)).toString());
assertEquals("-3.0+3.0i",new ComplexOf20145208(2,6).Minus(new ComplexOf20145208(5,3)).toString());
assertEquals("10.0+18.0i",new ComplexOf20145208(2,6).Multi(new ComplexOf20145208(5,3)).toString());
}
- 这个测试就是输出结果符合我的期待值,初步测试了我的代码:
- 但在仅仅一个测试还是不够的,我需要再输入一些错的测试值,进一步来验证程序的输出:
assertEquals("-7.0+9.0i",new ComplexOf20145208(2,6).Add(new ComplexOf20145208(5,3)).toString());
- 结果没有让我失望,测试出来我给的输出和输入不匹配的,并且IDEA给出了实际运行出来的结果:
- 两个测试就够了么?还是远远不够的,在找错误的时候可以多找找边缘值,这里是比较容易出现错误的地方,在复数的计算中,边缘值就是复数i的系数,我们可以测试一下如果系数等于0怎么办吧?代码如下:
assertEquals("0.0",new ComplexOf20145208(0,6).Multi(new ComplexOf20145208(5,0)).toString());
- 结果是测试通过的:
- 但是这样还是不够,测试一种错误的输出试试吧:
assertEquals("0.0+0.0i",new ComplexOf20145208(0,6).Multi(new ComplexOf20145208(5,0)).toString());
- 提是测试错误了,还给出了程序输出的内容:
- 但是一个程序的测试的范围很大,我刚刚测试的也只是很少的一部分,接下来我再进行一个测试:
assertEquals("18.0i",new ComplexOf20145208(0,6).Multi(new ComplexOf20145208(5,3)).toString());
- 既然复数的系数为0可以在输出的时候不显示i,那么让实数为0的时候应该只显示
18i
吧,但是在这里程序让我失望了,测试失败了,我也终于检测到了我的一个缺漏之处,就是没有设定实数为0但虚部不为零的情况:
- 发现了问题所在就将问题补足,在代码中添加条件限制,代码段如下:
public String toString(){
if (m_dImaginPart > 0) {
if (m_dRealPart==0)
return this.m_dImaginPart+"i";
else
return this.m_dRealPart + "+" + this.m_dImaginPart + "i";
} else if (m_dImaginPart < 0) {
if (m_dRealPart==0)
return this.m_dImaginPart+"i";
else
return this.m_dRealPart + this.m_dImaginPart + "i";
} else if (m_dImaginPart == 0) {
return this.m_dRealPart + "";
}
return "Wrong!";
}
- 这样我就限定了在实部为0的时候只输出虚部,测试通过:
- 到这里,我对我的代码的测试就基本完成了,当然,对于测试来说,还有很多测试的余地,但是在本次实验中就不做过多测试了。
(二)面向对象三要素
(1)抽象
- 抽象一词的本意是指人在认识思维活动中对事物表象因素的舍弃和对本质因素的抽取。抽象是人类认识复杂事物和现象时经常使用的思维工具,抽象思维能力在程序设计中非常重要,"去粗取精、化繁为简、由表及里、异中求同"的抽象能力很大程度上决定了程序员的程序设计能力。
- 抽象就是抽出事物的本质特征而暂时不考虑他们的细节。对于复杂系统问题人们借助分层次抽象的方法进行问题求解;在抽象的最高层,可以使用问题环境的语言,以概括的方式叙述问题的解。在抽象的较低层,则采用过程化的方式进行描述。在描述问题解时,使用面向问题和面向实现的术语。
- 程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。
(2)封装、继承与多态
- 面向对象(Object-Oriented)的三要素包括:封装、继承、多态。面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。
- 封装示例
public class Dog {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public String bark(){
return "汪汪";
}
public String toString(){
return "The Dog's color is " + this.getColor() +", and it shouts "+ this.bark() + "!";
}
}
- 检测示例
public class DogTest {
public static void main(String[] args) {
Dog d = new Dog();
d.setColor("Yellow");
getInfo(d);
}
public static void getInfo(Dog d) {
System.out.println(d.toString());
}
}
- 输出结果:
利用StarUML软件进行UML建模
- 选择建立Default Approach工程
- 选择Design Model
- 新建class
- 添加Attribute
- 进行封装
- 添加Operation
- 添加测试类并建立关系
- 在Tools->Java->Generate code...中生成代码,可能出现如下问题:
- 只需要在Model->Profiles... 将左边的Java Profile移到右边就可以了
- 然后就可以选择建模并保存到指定文件夹了:
(三)设计模式初步
(1)S.O.L.I.D原则
- SRP(Single Responsibility Principle,单一职责原则)
- OCP(Open-Closed Principle,开放-封闭原则)
- LSP(Liskov Substitusion Principle,Liskov替换原则)
- ISP(Interface Segregation Principle,接口分离原则)
- DIP(Dependency Inversion Principle,依赖倒置原则)
- 因为S.O.L.I.D原则在之前的学习中已经阐述了就不在赘述了。
(2)模式与设计模式
- 模式是某外在环境(Context) 下﹐对特定问题(Problem)的惯用解决之道(Solution)。模式必须使得问题明晰,阐明为什么用它来求解问题,以及在什么情况下有用,什么情况下不能起作用,每个模式因其重复性从而可被复用,本身有自己的名字,有可传授性,能移植到不同情景下。模式可以看作对一个问题可复用的专家级解决方法。计算机科学中有很多模式:
- GRASP模式
- 分析模式
- 软件体系结构模式
- 设计模式:创建型,结构型,行为型
- 管理模式: The Manager Pool 实现模式
- 界面设计交互模式
- …
- 这里面最重要的是设计模式,在面向对象中设计模式的地位可以和面向过程编程中的数据结构的地位相当。
(3)设计模式实示例
设计模式可以帮我们以最好的方式来设计系统。设计模式背后是抽象和SOLID原则。
设计模式有四个基本要素:
Pattern name:描述模式,便于交流,存档
Problem:描述何处应用该模式
Solution:描述一个设计的组成元素,不针对特例
Consequence:应用该模式的结果和权衡(trade-offs)
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 5min | 5% |
设计 | 15min | 15% |
代码实现 | 65min | 65% |
测试 | 10min | 10% |
分析总结 | 5min | 5% |
总结
- 在本次实验中,我学会了测试代码,虽然对于代码进行测试看上去很麻烦,感觉多此一举,但是就像老师所说的,代码中的bug越早越容易发现,如果拖到代码庞大之后再去编写测试的话,工作量太大了,而且一旦出现什么错误,找到BUG是一件十分困难的事情。
- 那么,通过单元测试,我们就可以很大程度的避免那些错误,也许现在的效果不明显,但是如果我们养成这个习惯,在以后编写大量的代码程序的时候就可以帮助我们队代码查缺补漏,大大的节约我们的时间和成本。