实验二 Java面向对象程序设计
实验内容
1. 初步掌握单元测试和TDD
2. 理解并掌握面向对象三要素:封装、继承、多态
3. 初步掌握UML建模
4. 熟悉S.O.L.I.D原则
5. 了解设计模式
实验步骤
(一)单元测试
(1) 三种代码
- 伪代码
百分制转五分制: 如果成绩小于60,转成“不及格” 如果成绩在60与70之间,转成“及格” 如果成绩在70与80之间,转成“中等” 如果成绩在80与90之间,转成“良好” 如果成绩在90与100之间,转成“优秀” 其他,转成“错误”
- 产品代码
public class MyUtil{ public static String percentage2fivegrade(int grade){ //如果成绩小于60,转成“不及格” if (grade < 60) return "不及格"; //如果成绩在60与70之间,转成“及格” else if (grade < 70) return "及格"; //如果成绩在70与80之间,转成“中等” else if (grade < 80) return "中等"; //如果成绩在80与90之间,转成“良好” else if (grade < 90) return "良好"; //如果成绩在90与100之间,转成“优秀” else if (grade < 100) return "优秀"; //其他,转成“错误” else return "错误"; } }
- 测试代码
1、50分测试:
public class MyUtilTest {
public static void main(String[] args) {
// 百分制成绩是50时应该返回五级制的“不及格”
if(MyUtil.percentage2fivegrade(50) != "不及格")
System.out.println("test failed!");
else
System.out.println("test passed!");
}
}
运行结果:
2、正常情况测试:
public class MyUtilTest {
public static void main(String[] args) {
//测试正常情况
if(MyUtil.percentage2fivegrade(55) != "不及格")
System.out.println("test failed!");
else if(MyUtil.percentage2fivegrade(65) != "及格")
System.out.println("test failed!");
else if(MyUtil.percentage2fivegrade(75) != "中等")
System.out.println("test failed!");
else if(MyUtil.percentage2fivegrade(85) != "良好")
System.out.println("test failed!");
else if(MyUtil.percentage2fivegrade(95) != "优秀")
System.out.println("test failed!");
else
System.out.println("test passed!");
}
}
运行结果:
3、异常情况测试:
public class MyUtilTest {
public static void main(String[] args) {
//测试出错情况
if(MyUtil.percentage2fivegrade(-10) != "错误")
System.out.println("test failed 1!");
else if(MyUtil.percentage2fivegrade(115) != "错误")
System.out.println("test failed 2!");
else
System.out.println("test passed!");
}
}
运行结果:
4、增加判断负分的情况:
public class MyUtil{
public static String percentage2fivegrade(int grade){
//如果成绩小于0,转成“错误”
if ((grade < 0))
return "错误";
//如果成绩小于60,转成“不及格”
else if (grade < 60)
return "不及格";
//如果成绩在60与70之间,转成“及格”
else if (grade < 70)
return "及格";
//如果成绩在70与80之间,转成“中等”
else if (grade < 80)
return "中等";
//如果成绩在80与90之间,转成“良好”
else if (grade < 90)
return "良好";
//如果成绩在90与100之间,转成“优秀”
else if (grade < 100)
return "优秀";
//如果成绩大于100,转成“错误”
else
return "错误";
}
}
运行结果:
5、测试边界情况:
public class MyUtilTest {
public static void main(String[] args) {
//测试边界情况
if(MyUtil.percentage2fivegrade(0) != "不及格")
System.out.println("test failed 1!");
else if(MyUtil.percentage2fivegrade(60) != "及格")
System.out.println("test failed 2!");
else if(MyUtil.percentage2fivegrade(70) != "中等")
System.out.println("test failed 3!");
else if(MyUtil.percentage2fivegrade(80) != "良好")
System.out.println("test failed 4!");
else if(MyUtil.percentage2fivegrade(90) != "优秀")
System.out.println("test failed 5!");
else if(MyUtil.percentage2fivegrade(100) != "优秀")
System.out.println("test failed 6!");
else
System.out.println("test passed!");
}
}
运行结果:
6、增加100为优秀的情况:
public class MyUtil{
public static String percentage2fivegrade(int grade){
//如果成绩小于0,转成“错误”
if ((grade < 0))
return "错误";
//如果成绩小于60,转成“不及格”
else if (grade < 60)
return "不及格";
//如果成绩在60与70之间,转成“及格”
else if (grade < 70)
return "及格";
//如果成绩在70与80之间,转成“中等”
else if (grade < 80)
return "中等";
//如果成绩在80与90之间,转成“良好”
else if (grade < 90)
return "良好";
//如果成绩在90与100之间,转成“优秀”
else if (grade <= 100)
return "优秀";
//如果成绩大于100,转成“错误”
else
return "错误";
}
}
运行结果:
(2) TDD(Test Driven Devlopment, 测试驱动开发)
先写测试代码,然后再写产品代码的开发方法叫“测试驱动开发”(TDD)。TDD的一般步骤如下:
- 明确当前要完成的功能,记录成一个测试列表
- 快速完成编写针对此功能的测试用例
- 测试代码编译不通过(没产品代码呢)
- 编写产品代码
- 测试通过
- 对代码进行重构,并保证测试通过(重构下次实验练习)
- 循环完成所有功能的开发
具体示例:
1 创建新的项目
创建名为UnitTestingApp的Java项目。
2 创建一个类进行测试
创建一个新的类用于测试。
添加方法sayHello返回Hello字符串。
3 创建测试源根目录
为了不将测试添加到源中,用户可以创建根目录。在这种情况下测试将从产品代码中分离出来。
创建一个测试源根目录。
4 创建一个测试类
IntelliJ IDEA提供了一个快捷操作Cmd + Shift + T作为类和测试之间的导航。同时允许用户在那里创建一个测试类。
选择JUnit 4作为单元测试库。IntelliJ IDEA将提供到件这个库添加到模块中。选择生成setUp和sayHello的方法。
当测试类生成后,我们可以为我们的测试方法testSayHello添加代码。
5 运行测试
现在我们可以通过右键菜单在这个类上运行‘MyClassTest‘来进行测试,或通过Run → Edit Configurations来进行。
结果将在Run工具窗口进行显示。
(二)面向对象三要素
(1)抽象
- 抽象一词的本意是指人在认识思维活动中对事物表象因素的舍弃和对本质因素的抽取。
- 抽象是人类认识复杂事物和现象时经常使用的思维工具,抽象思维能力在程序设计中非常重要,"去粗取精、化繁为简、由表及里、异中求同"的抽象能力很大程度上决定了程序员的程序设计能力。
- 抽象就是抽出事物的本质特征而暂时不考虑他们的细节,对于复杂系统问题人们借助分层次抽象的方法进行问题求解。在抽象的最高层,可以使用问题环境的语言,以概括的方式叙述问题的解;在抽象的较低层,则采用过程化的方式进行描述。在描述问题解时,使用面向问题和面向实现的术语。
- 程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。
(2)封装、继承与多态
- 面向对象(Object-Oriented)的三要素包括:封装、继承、多态。
- 面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。
- OOA根据抽象关键的问题域来分解系统,关注是什么(what)。
- OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。
- 贯穿OOA、OOD和OOP的主线正是抽象。
(三)设计模式初步
熟悉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,依赖倒置原则)
(四)练习
使用TDD的方式设计关实现复数类Complex
-
伪代码:
定义复数的实部和虚部(均为双精度); 构造方法分别取得复数的实部和虚部; 构造两个复数相加的函数,并返回结果; 构造两个复数相减的函数,并返回结果; 构造两个复数相乘的函数,并返回结果; 构造两个复数相除的函数,并返回结果;
-
产品代码:
public class Complex { double real,img; //实部和虚部 public Complex() //默认构造方法 { this.real=0; this.img =0; } public Complex(double real,double img) //带参数的构造方法 { this.real=real; this.img =img; } public double getReal() { return this.real; } //得到实部 public double getImage() { return this.img; } //得到虚部 public double getReal(Complex c) { return c.real; } //得到复数c的实部 public double getImage(Complex c) { return c.img; } //得到复数c的虚部 public void setReal (double real) { this.real=real; } //设置实部 public void setImage(double img) { this.img =img; } //设置虚部 public Complex addComplex(Complex a,Complex b) //两个复数相加,结果返回 { Complex temp =new Complex(); temp.real=a.real+b.real; temp.img =a.img +b.img; return temp; } public Complex decComplex(Complex a,Complex b) //两个复数相减,结果返回 { Complex temp = new Complex(); temp.real = a.real - b.real; temp.img = a.img - b.img; return temp; } public Complex mulComplex(Complex a,Complex b) //两个复数相乘,结果返回 { Complex temp = new Complex(); temp.real = a.real*b.real-a.img*b.img; temp.img = a.real*b.img+a.img*b.real; return temp; } public Complex divComplex(Complex a,Complex b) //两个复数相除,结果返回 { Complex temp = new Complex(); temp.real=(a.real*b.real+a.img*b.img)/(b.real*b.real+b.img*b.img); temp.img =(a.img*b.real-a.real*b.img)/(b.real*b.real+b.img*b.img); return temp; } public void printComplex() { System.out.println(""+this.real+"+"+this.img+"i"); } //输出结果 public String toString() { String fin=" "; if(img>0) { fin = real+"+"+img+"i"; } else if(img<0) { fin = real+ ""+img+"i"; } else { fin = fin; } return fin; }
-
测试代码:
public static void main(String[] args) { Complex cc = new Complex(2, 6); cc.printComplex(); Complex dd = new Complex(1, 1); dd.printComplex(); System.out.println("-----------------"); Complex ff = new Complex(); ff = ff.addComplex(cc, dd); ff.printComplex(); ff = ff.decComplex(cc, dd); ff.printComplex(); ff = ff.mulComplex(cc, dd); ff.printComplex(); ff = ff.divComplex(cc, dd); ff.printComplex(); System.out.println("-----------------"); }
-
用starUML软件建模复数类Complex:
-
分块测试截图:
1、加法:
2、减法:
3、乘法:
4、除法:
-
PSP时间:
步骤 | 耗时 | 百分比 |
---|---|---|
需求分析 | 30min | 16.7% |
设计 | 60min | 33.3% |
代码实现 | 45min | 25% |
测试 | 15min | 8.3% |
分析总结 | 30min | 16.7% |
-
单元测试的好处:
1、它是一种验证行为。
程序中的每一项功能都是测试来验证它的正确性。它为以后的开发提供支缓。就算是开发后期,我们也可以轻松的增加功能或更改程序结构,而不用 担心这个过程中会破坏重要的东西。而且它为代码的重构提供了保障。这样,我们就可以更自由的对程序进行改进。
2、它是一种设计行为。
编写单元测试将使我们从调用者的角度观察、思考。特别是先写测试(test-first),迫使我们把程序设计成易于调用和可测试的,即迫使我们解除软件中的耦合。
3、它是一种编写文档的行为。
单元测试是一种无价的文档,它是展示函数或类如何使用的最佳文档。这份文档是可编译、可运行的,并且它保持最新,永远与代码同步。
4、它具有回归性。
自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地的快速运行测试。