实验二Java面向对象程序设计
北京电子科技学院(BESTI)
实 验 报 告
课程:Java程序设计 班级:1352 姓名:王国伊 学号:20135207
成绩: 指导教师:娄嘉鹏 实验日期:2015.5.8
实验密级:无 预习程度: 实验时间:15:30~18:00
仪器组次:07 必修/选修:选修 实验序号:02
实验名称: Java面向对象程序设计
实验目的与要求: 1. 初步掌握单元测试和TDD; 2. 理解并掌握面向对象三要素:封装、继承、多态; 3. 初步掌握UML建模; 4. 熟悉S.O.L.I.D原则; 5. 了解设计模式。
实验仪器:
名称 |
型号 |
数量 |
PC机 |
Lenovo Z485 |
1 |
Linux虚拟机 |
Ubuntu(32bit) |
1 |
实验步骤
一、单元测试
- 三种代码
编程是智力活动,不是打字,编程前要把干什么、如何干想清楚才能把程序写对、写好。当我们想用程序解决问题时,要会写三种码:
- 伪代码
- 产品代码
- 测试代码
需求:我们要在一个MyUtil类中解决一个百分制成绩转成“优、良、中、及格、不及格”五级制成绩的功能。
伪代码:
产品代码:
测试代码:
经过几次修改后得到可以通过自己测试的代码,即能够实现需求的代码。
这时测试都符合预期了,我们把MyUtil.java提供给别人使用时,他人可以按照要求实现需求。为保证单元测度是充分的,我们的一般要求是测试代码要比产品代码多。
2.TDD(Test Driven Devlopment, 测试驱动开发)
TDD的一般步骤如下:
- 明确当前要完成的功能,记录成一个测试列表
- 快速完成编写针对此功能的测试用例
- 测试代码编译不通过(没产品代码呢)
- 编写产品代码
- 测试通过
- 对代码进行重构,并保证测试通过(重构下次实验练习)
- 循环完成所有功能的开发
基于TDD,我们不会出现过度设计的情况,需求通过测试用例表达出来了,我们的产品代码只要让测试通过就可以了。 Java中有单元测试工具JUnit来辅助进行TDD,我们用TDD的方式把前面百分制转五分制的例子重写一次,体会一下有测试工具支持的开发的好处。
具体操作如下:
打开Eclipse,单击File->New->Java Project新建一个TDDDemo
在弹出的菜单中选定New->Source Folder新建一个测试目录test
我们把鼠标放到test目录上,单击右键,在弹出的菜单中选定New->JUnit Test Case新建一个测试用例类MyUtilTest
我们增加第一个测试用例testNormal,注意测试用例前一定要有注解@Test,测试用例方法名任意
图中的红叉说明代码存在语法错误,原因很简单,MyUtil类还不存在,类中的percentage2fivegrade方法也不存在,我们在TDDDemo的src目录中新建一个MyUtil的类,并实现percentage2fivegrade方法,如下图所示:
测试结果出现了一个红条(red bar),说明测试没通过,
测试结果出现了一个绿条(green bar),说明测试通过了。
TDD的编码节奏是:
- 增加测试代码,JUnit出现红条
- 修改产品代码
- JUnit出现绿条,任务完成
二、面向对象三要素
1.抽象
抽象一词的本意是指人在认识思维活动中对事物表象因素的舍弃和对本质因素的抽取。抽象是人类认识复杂事物和现象时经常使用的思维工具,抽象思维能力在程序设计中非常重要,"去粗取精、化繁为简、由表及里、异中求同"的抽象能力很大程度上决定了程序员的程序设计能力。
抽象就是抽出事物的本质特征而暂时不考虑他们的细节。对于复杂系统问题人们借助分层次抽象的方法进行问题求解;在抽象的最高层,可以使用问题环境的语言,以概括的方式叙述问题的解。在抽象的较低层,则采用过程化的方式进行描述。在描述问题解时,使用面向问题和面向实现的术语。程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。
我们举个例子说明一下。比如有了以下Java代码:
System.out.println(1); System.out.println(2); System.out.println(3);
可以打印出“1,2,3”,想打引“1,2,3,4”怎么办?同学们的做法大多是把上面的代码拷贝下来,再加一行:
System.out.println(1); System.out.println(2); System.out.println(3); System.out.println(4);
这就是没有学会过程抽象的做法“拷贝粘贴”式开发。解决问题没?解决了,但有问题,比如想打印出“1..100"怎么办?粘贴100行?这两段代码有三行重复的代码,违反了常见的一个编程原则DRY(Don't Repeat Yourself),解决的方法是进行过程抽象,写一个函数printn:
public void printn(int n){ for(int i=1; i<=n; i++) System.out.println(n); }
上面两段代码就可以用;
printn(3); printn(4);
代替了,打印出“1..100"也很简单,只要调用printn(100);就行了。
2.封装、继承与多态
面向对象(Object-Oriented)的三要素包括:封装、继承、多态。面向对象的思想涉及到软件开发的各个方面,如面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA根据抽象关键的问题域来分解系统,关注是什么(what)。OOD是一种提供符号设计系统的面向对象的实现过程,用非常接近问题域术语的方法把系统构造成“现实世界”的对象,关注怎么做(how),通过模型来实现功能规范。OOP则在设计的基础上用编程语言(如Java)编码。贯穿OOA、OOD和OOP的主线正是抽象。 OOD中建模会用图形化的建模语言UML(Unified Modeling Language),UML是一种通用的建模语言,我们实验中使用umbrello进行建模,Windows中推荐大家使用 StarUML。
过程抽象的结果是函数,数据抽象的结果是抽象数据类型(Abstract Data Type,ADT),类可以作具有继承和多态机制的ADT。数据抽象才是OOP的核心和起源。
OO三要素的第一个要素是封装,封装就是将数据与相关行为包装在一起以实现信息就隐藏。Java中用类进行封装,比如一个Dog类:
封装实际上使用方法(method)将类的数据隐藏起来,控制用户对类的修改和访问数据的程度,从而带来模块化(Modularity)和信息隐藏(Information hiding)的好处;接口(interface)是封装的准确描述手段。 Dog类通过使用类和访问控制(private,public)隐藏了属性color,开放了接口setColor(),getColor(),bark()和toString。Dog类是一个模块,我们可以通过下面的代码使用它,测试代码与运行结果如下:
我们可以用UML中的类图来描述类Dog,首先我们在实验楼的环境中打开shell,在命令行中输入umbrello,打开UML建模软件umbrello,如下图所示:
先单击工具栏上的类图标,再在class diagram(类图)中单击一下,会弹出一个圣诞框,输入类名Dog, 我们把鼠标放到Dog类上,单击右键,选择Properties,在弹出的对话框中的Display中去掉Public Only选项,我们把鼠标放到Dog类上,单击右键,选择New->Attribute,在弹出的对话框中的填好Type,Name,并选好Visibility,我们把鼠标放到Dog类上,单击右键,选择New->Operation,在弹出的对话框中的填好Type,Name,并选好Visibility,我们可以看到,在UML 里,一个类的属性能显示它的名字,类型,初始化值,属性也可以显示private,public,protected。 类的方法能显示它们的方法名,参数,返回类型,以及方法的private,public,protected属性。其中:
- +表示public
- #表示 protected
- -表示 private
使用UML可以让我们不必关注细节。同样,我们可以建立一个Cat类这时的测试类如以下UML图所示:
注意:UML类图要展示类之间的静态关系,AnimalTest类依赖Dog类和Cat类,UML中依赖用带箭头的直线表示。 对应的测试代码和运行结果如下图所示:
我们看到Dog类和Cat类都有Color属性和相应的setter和getter方法,明显违反了前面提到的DRY原则,我们可以通过继承解决这个问题,把Color属性和相应的setter和getter方法放到父类Animal中,如以下UML较图所示:
UML类图中继承的表示法,是用一个带三角的直线指向父类,通过继承,我们消除了Dog类和Cat类中的重复代码,符合DRY的要求。 继承指一个类的定义可以基于另外一个已经存在的类,即子类基于父类,从而实现父类代码的重用。既存类称作基类、超类、父类(base class、super class、parent class),新类称作派生类、继承类、子类(derived class、inherited class、child class)。继承关系表达了”Is a kind of“的关系,称为“ISA”关系。继承的关键在于确认子类为父类的一个特殊类型。继承是实现软件可重用的根基,是提高软件系统的可扩展性与可维护性的主要途径。 如上面所示,以封装为基础,继承可以实现代码复用,需要注意的是,继承更重要的作用是实现多态。面向对象中允许不同类的对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式,我们称此现象为多态性。Java中,多态是指不同的类对象调用同一个签名的成员方法时将执行不同代码的现象。多态是面向对象程序设计的灵活性和可扩展性的基础。我们再看看上一个类图,我们可以进一步抽象,把Dog类中的bark()和Cat类中的meow()抽象成一个抽象方法shout(),Dog类和Cat类中覆盖这个方法,如以下UML图所示:
UML类图中的Animal类中的shout()方法是抽象方法,是斜体的,Animal类是抽象类,也是斜体的。 对应的代码及运行结果如下:
在Java中,当我们用父类声明引用,用子类生成对象时,多态就出现了
三、设计模式初步
1. S.O.L.I.D原则
面向对象三要素是“封装、继承、多态”,任何面向对象编程语言都会在语法上支持这三要素。如何借助抽象思维用好三要素特别是多态还是非常困难的,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,依赖倒置原则)
1).OCP是OOD中最重要的一个原则,OCP的内容是:
- software entities (class, modules, function, etc.) should open for extension,but closed for modification.
- 软件实体(类,模块,函数等)应该对扩充开放,对修改封闭。
对扩充开放(Open For Extension )要求软件模块的行为必须是可以扩充的,在应用需求改变或需要满足新的应用需求时,我们要让模块以不同的方式工作; 对修改封闭(Closed for Modification )要求模块的源代码是不可改动的,任何人都不许修改已有模块的源代码。 基于OCP,利用面向对象中的多态性(Polymorphic),更灵活地处理变更拥抱变化,OCP可以用以下手段实现:(1)抽象和继承,(2)面向接口编程。
用户现大需要一个Triangle模块是一个合理的要求,由于我们使用了多态,原先的模块不需要改变,只要新增加一个模块Triangle就可以了,如下图所示:
这个图形系统是符合OCP原则的。
2).SRP的内容是:
- There should never be more than one reason for a class to change
- 决不要有一个以上的理由修改一个类
3).LSP的内容是:
- Subtypes must be substitutable for their base types
- Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
- 子类必须可以被其基类所代
- 使用指向基类的指针或引用的函数,必须能够在不知道具体派生类对象类型的情况下使用它(LSP是Liskov女士提出的)
4).LSP的核心思想是父类型对象可以被子类型对象所取代。我们前面举的Animal,Dog,Cat的那个例子是符合LSP原则的。
5).ISP的内容是:
- Clients should not be forced to depend upon interfaces that they do not use
- 客户不应该依赖他们并未使用的接口
6).DIP的内容是:
- High level modules should not depend upon low level modules. Both should depend upon abstractions
- Abstractions should not depend upon details. Details should depend upon abstractions
- 高层模块不应该依赖于低层模块。二者都应该依赖于抽象
- 抽象不应该依赖于细节。细节应该依赖于抽象
2. 模式与设计模式
模式是某外在环境(Context) 下﹐对特定问题(Problem)的惯用解决之道(Solution)。模式必须使得问题明晰,阐明为什么用它来求解问题,以及在什么情况下有用,什么情况下不能起作用,每个模式因其重复性从而可被复用,本身有自己的名字,有可传授性,能移植到不同情景下。模式可以看作对一个问题可复用的专家级解决方法。计算机科学中有很多模式:
- GRASP模式
- 分析模式
- 软件体系结构模式
- 设计模式:创建型,结构型,行为型
- 管理模式: The Manager Pool 实现模式
- 界面设计交互模式
- …
这里面最重要的是设计模式,在面向对象中设计模式的地位可以和面向过程编程中的数据结构的地位相当。
3.设计模式实示例
设计模式四个基本元素
Pattern name:描述模式,便于交流,存档
Problem:描述何处应用该模式
Solution:描述一个设计的组成元素,不针对特例
Consequence:应用该模式的结果和权衡
四、使用TDD的方式设计关实现复数类Complex
步骤如下:
1、编写计算复数类Complex,分为加减乘除四种情况。
public class Complex {
private double m;
private double n;
public Complex(double m,double n) {
this.m = m;
this.n = n;
}
public String add(Complex b) {
Complex x = new Complex(m + b.m, n + b.n);
System.out.println("a" + "+" + "b" + "=" + x);
return "true";
}
public String minus(Complex b) {
Complex x = new Complex(m - b.m, n - b.n);
System.out.println("a" + "-" + "b" + "=" + x);
return "true";
}
public String multiply(Complex b) {
Complex x = new Complex(m * b.m - n * b.n, m*b.n + n * b.m);
System.out.println("a" + "*" + "b" + "=" + x);
return "true";
}
public String divide(Complex b) {
double d = Math.sqrt(b.m * b.m) + Math.sqrt(b.n * b.n);
Complex x = new Complex((m * b.m + n * b.n) / d,Math.round((m * b.n - n * b.m)/d));
System.out.println("a" + "/" + "b" + "=" + x);
return "true";
}
public String toString() {
String str = "";
if(n>0)
str = "(" + m + "+" + n + "i" + ")";
if(n==0)
str = "(" + m + ")";
if(n<0)
str = "(" + m + n + "i" +")";
return str;
}
}
2、编写ComplexDemo类,传入a、b两个复数的实部和虚部,调用Complex类进行计算。