2018-2019-20175205实验二面向对象程序设计《Java开发环境的熟悉》实验报告
实验要求
- 没有Linux基础的同学建议先学习《Linux基础入门(新版)》《Vim编辑器》 课程
- 完成实验、撰写实验报告,实验报告以博客方式发表在博客园,注意实验报告重点是运行结果,遇到的问题(工具查找,安装,使用,程序的编辑,调试,运行等)、解决办法(空洞的方法如“查网络”、“问同学”、“看书”等一律得0分)以及分析(从中可以得到什么启示,有什么收获,教训等)。报告可以参考范飞龙老师的指导
- 严禁抄袭,有该行为者实验成绩归零,并附加其他惩罚措施。
- 请大家先在实验楼中的~/Code目录中用自己的学号建立一个目录,代码和UML图要放到这个目录中,截图中没有学号的会要求重做,然后跟着下面的步骤练习。
实验步骤
(一)单元测试
- 三种代码
- 伪代码:将问题抽象出来,写出需要计算机执行的步骤
- 产品代码:用特定的编程语言翻译
- 测试代码:测试代码有没有问题
- 单元测试:对类实现的测试
- 点击
New->Directory
新建一个test目录,再右键点击设置环境变量,选择Mark Directory->Test Sources Roo
t即可
- 点击
-
设计一个测试用例:将正常情况,异常情况,边界情况一一排查,检查出所有bug并修改,才能保证所写代码比较健全准确
-
正常情况
-
边界情况:一般容易遗漏边界情况,而且容易出错
-
异常情况 ->依据所出现的异常情况,就应该针对此问题修改源代码,直至测试成功。
-
(二)以 TDD的方式研究学习StringBuffer
- TDD(Test Driven Devlopment, 测试驱动开发):先写测试代码,再写产品代码。
- 步骤:
- 明确当前要完成的功能,记录成一个测试列表
- 快速完成编写针对此功能的测试用例
- 测试代码编译不通过(没产品代码呢)
- 编写产品代码
- 测试通过
- 对代码进行重构,并保证测试通过(重构下次实验练习)
- 循环完成所有功能的开发
- 安装JUnit插件
-
下载完成后,在IDEA中新建空类,鼠标单击类名会出现一个灯泡状图标,单击图标或按Alt + Enter,在弹出的菜单中选择Create Test
-
选择创建JUnit3的测试用例
- 如果TestCase是红色的,需要在IDEA中的项目(模块)中加入junit.jar包,junit.jar包的位置可以在Everything中查找
- 在弹出的对话框中选择Dependancies标签页,单击+号,选择JARs or Directories...,输入上面找到的
C:Users13015AppDataLocalJetBrainsToolboxappsIDEA-Uch-1171.4073.35libjunit.jar
。
StringBuffer
-
capacity返回的是目前的最大容量而length返回的是字符串长度
- 默认值为16
- 根据capacity的构造方法,可以指定初始容量
-
charAt返回该位置上的字符
-
indexOf返回第一次出现的指定子字符串在该字符串中的索引
StringBuilder、StringBuffer、String类之间的关系
-
String类:String的值是不可变的,因此每次对String操作都会生成新的String对象,浪费大量内存空间。为理解这个,我从书上摘取了一个小栗子~a最后指向56EF,但最后12AB和56EF地址中的数据仍然存在,因此String的操作都是改变赋值地址而不是改变值操作。
String a = "你好"
a = "boy"
a = "12.97"
-
StringBuffer是可变类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量,非常人性化。
StringBuffer buf=new StringBuffer(); //分配默认长16字节的字符缓冲区
StringBuffer buf=new StringBuffer(512); //分配长512字节的字符缓冲区
StringBuffer buf=new StringBuffer("this is a test")//在缓冲区中存放了字符串,并在后面预留了16字节的空缓冲区。
-
StringBuilder与StringBuffer功能基本相似,主要区别在于StringBuffer类的方法是多线程、安全的,而StringBuilder不是线程安全的,相比而言,StringBuilder类会略微快一点。
- 多线程:每一个任务称为一个线程。可以同时运行一个以上线程的程序称为多线程程序。
-
总结
- String:适用于少量的字符串操作的情况
- StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况
- StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况
(三)面向对象三要素
- 抽象:抽出事物的本质特征而暂不考虑细节,对于复杂的问题分层求解
- 过程抽象:结果是函数
- 数据抽象:结果是抽象数据类型
- 封装,继承与多态(面向对象的三要素)
- 封装:将数据与相关行为包装在一起以实现信息隐藏,java中使用类进行封装,接口是封装准确描述手段
- 继承:关键在于确认子类为父类的一个特殊类型,以封装为基础,继承可以实现代码复用,继承更重要的作用是实现多态。
- 多态:同一消息可以根据发送对象的不同而采用多种不同的行为方式
(四)设计模式初步
- 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,依赖倒置原则)
- 设计模式
- Pattern name:描述模式,便于交流,存档
- Problem:描述何处应用该模式
- Solution:描述一个设计的组成元素,不针对特例
- Consequence:应用该模式的结果和权衡(trade-offs)
(五)练习
- 练习题目1:让系统支持Double类,并在MyDoc类中添加测试代码表明添加正确,提交测试代码和运行结的截图,加上学号水印
- 产品代码
// Sever Classer
abstract class Data{
public abstract void DisplayValue();
}
class Integer extends Data{
int value;
Integer(){
value = 100;
}
public void DisplayValue(){
System.out.println(value);
}
}
class Double extends Data{
double value;
Double(){
value = 5.0;
}
public void DisplayValue(){
System.out.println(value);
}
}
// Pattern Classes
abstract class Factory{
public abstract Data CreateDataObject();
}
class IntFactory extends Factory{
public Data CreateDataObject(){
return new Integer();
}
}
class DoubleFactory extends Factory{
public Data CreateDataObject(){
return new Double();
}
}
//Client classes
class Document {
Data pd;
Document(Factory pf){
pd = pf.CreateDataObject();
}
public void DisplayData(){
pd.DisplayValue();
}
}
public class MyDoc {
static Document d;
static Document f;
public static void main(String[] args) {
d = new Document(new IntFactory());
d.DisplayData();
f = new Document(new DoubleFactory());
f.DisplayData();
}
}
-
运行结果
-
练习题目2:以TDD的方式开发一个复数类Complex,
- 伪代码
// 定义属性并生成getter,setter
double RealPart;
double ImagePart;
// 定义构造函数
public Complex()
public Complex(double R,double I)
//Override Object
public boolean equals(Object obj)
public String toString()
// 定义公有方法:加减乘除
Complex ComplexAdd(Complex a)
Complex ComplexSub(Complex a)
Complex ComplexMulti(Complex a)
Complex ComplexDiv(Complex a)
- 产品代码
public class MyComplex{
//定义属性并生成getter,setter
private double realPart;
private double imagePart;
public double getRealPart(){
return realPart;
}
public double getImagePart(){
return imagePart;
}
//定义构造函数
public MyComplex(){}
public MyComplex(double r,double i){
realPart = r;
imagePart = i;
}
//Override Object
@Override
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(!(obj instanceof MyComplex)) {
return false;
}
MyComplex complex = (MyComplex) obj;
if(complex.realPart != ((MyComplex) obj).realPart) {
return false;
}
if(complex.imagePart != ((MyComplex) obj).imagePart) {
return false;
}
return true;
}
@Override
public String toString(){
String s = new String();
if(imagePart >0 && realPart!=0){
s = ""+getRealPart()+"+"+getImagePart()+"i";
}
if(imagePart >0 && realPart==0){
s= getImagePart()+"i";
}
if(imagePart <0 &&realPart!=0)
{
s = ""+getRealPart()+getImagePart()+"i";
}
if(imagePart <0 && realPart==0){
s= getImagePart()+"i";
}
if(imagePart ==0 &&realPart!=0)
{
s = ""+getRealPart();
}
if(imagePart ==0 && realPart==0){
s= "0";
}
return s;
}
//定义公有方法:加减乘除
public MyComplex complexAdd(MyComplex a){
return new MyComplex(realPart+a.realPart, imagePart +a.imagePart);
}
public MyComplex complexSub(MyComplex a){
return new MyComplex(realPart-a.realPart, imagePart -a.imagePart);
}
public MyComplex complexMulti(MyComplex a){
return new MyComplex(realPart * a.realPart - imagePart * a.imagePart, realPart * a.imagePart + imagePart * a.realPart);
}
public MyComplex complexDiv(MyComplex a){
return new MyComplex((realPart * a.realPart + imagePart * a.imagePart) / (a.imagePart * a.imagePart + a.realPart * a.realPart), (imagePart * a.realPart - realPart * a.imagePart) / (a.realPart * a.realPart + a.realPart * a.realPart));
}
}
- 测试代码
import junit.framework.TestCase;
import org.junit.Test;
public class MyComplexTest extends TestCase {
MyComplex a = new MyComplex(2.0,4.0);
MyComplex b = new MyComplex(0.0,-3.0);
MyComplex c = new MyComplex(-5.0,0.0);
@Test
public void testgetRealpart(){
assertEquals(2.0,a.getRealPart());
assertEquals(0.0,b.getRealPart());
assertEquals(-5.0,c.getRealPart());
}
@Test
public void testgetImagePart(){
assertEquals(4.0,a.getImagePart());
assertEquals(-3.0,b.getImagePart());
assertEquals(0.0,c.getImagePart());
}
@Test
public void testMyComplexAdd(){
String q = a.complexAdd(b).toString();
String w = b.complexAdd(c).toString();
String e = c.complexAdd(a).toString();
assertEquals("2.0+1.0i",q);
assertEquals("-5.0-3.0i",w);
assertEquals("-3.0+4.0i",e);
}
@Test
public void testMyComplexSub(){
String r = a.complexSub(b).toString();
String t = b.complexSub(c).toString();
String y = c.complexSub(a).toString();
assertEquals("2.0+7.0i",r);
assertEquals("5.0-3.0i",t);
assertEquals("-7.0-4.0i",y);
}
@Test
public void testMyComplexMulti(){
String u = a.complexMulti(b).toString();
String i = b.complexMulti(c).toString();
String o = c.complexMulti(a).toString();
assertEquals("12.0-6.0i",u);
assertEquals("15.0i",i);
assertEquals("-10.0-20.0i",o);
}
@Test
public void testMyComplexDiv(){
String p = c.complexDiv(a).toString();
assertEquals("-0.5+2.5i",p);
}
@Test
public void testtoString(){
assertEquals("2.0+4.0i",a.toString());
assertEquals("-3.0i",b.toString());
assertEquals("-5.0",c.toString());
}
}
- 运行结果
实验中遇到的问题
Q: junit 使用org.junit不存在,点到代码中红色的部分显示:Cannot resolve symbol 'junit'
A:File -> Project Struct... -> Libraies -> 点击绿色的加号 -> Java -> 找到 IDEA 安装路径下的 Lib 中的junit-4.12 ->点击OK
Q:在对append进行测试的时候,明明期望的值和实际值相同,但还是测试失败
A:将StringBuffer转为字符串再比较,即可得出答案
public void testappend() throws Exception{
String q,w,e;
a = a.append("abc");
b = b.append("abc");
c = c.append(ch,2,3);
q = a.toString();
w = b.toString();
e = c.toString();
assertEquals("StringBufferabc",q);
assertEquals("StringBufferStringBufferabc",w);
assertEquals("StringBufferStringBufferStringBufferc12",e);
}
- 单元测试的好处
单元测试可以帮助我测试一些边界,异常情况,就比如说我的结对项目,运行完之后往往找不到出错点,有很多细节问题都没有考虑到代码中,这是通过单元测试检测出一些bug,因此可以对产品代码加以修改和补充,完善产品代码,写出更加健全符合要求的程序。