20145221 《Java程序设计》实验报告二:Java面向对象程序设计

20145221 《Java程序设计》实验报告二:Java面向对象程序设计

实验要求

  • 初步掌握单元测试和TDD
  • 理解并掌握面向对象三要素:封装、继承、多态
  • 初步掌握UML建模
  • 熟悉S.O.L.I.D原则
  • 了解设计模式

实验内容

  • 单元测试
  • 面向对象三要素
  • 设计模式初步
  • 练习

实验步骤

单元测试

三种代码

  • 伪代码:伪代码从意图层面来解决问题,是产品代码最自然的、最好的注释。
百分制转五分制:
   如果成绩小于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 "错误";
   }
}
  • 测试代码:用以对产品代码进行测试的代码,来判断代码的可靠性。边界情况往往容易出错,我们可以优先测试边界情况。
    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!"); 
        }
    }

TDD(Test Driven Devlopment, 测试驱动开发)

  • 概念:先写测试代码,然后再写产品代码的开发方法

  • 一般步骤如下:

    • 明确当前要完成的功能,记录成一个测试列表
    • 快速完成编写针对此功能的测试用例
    • 测试代码编译不通过(没产品代码呢)
    • 编写产品代码
    • 测试通过
    • 对代码进行重构,并保证测试通过(重构下次实验练习)
    • 循环完成所有功能的开发
  • TDD模式测试代码的编写和调试结果

  • 在编好的代码下右键->go to->Test

  • 在MyUtilTest类中运行,得到如下所示,绿色代表被执行部分测试通过

面向对象三要素

抽象

  • 抽象能力是指"去粗取精、化繁为简、由表及里、异中求同"的能力。
  • 程序设计中,抽象包括两个方面,一是过程抽象,二是数据抽象。

封装、继承与多态

  • 面向对象(Object-Oriented)的三要素包括:封装、继承、多态。包括面向对象分析(OOA)、面向对象设计(OOD)、面向对象编程实现(OOP)。OOA关注是什么(what),OOD关注怎么做(how),OOP在设计的基础上用编程语言(如Java)编码。
封装:
  • 将数据与相关行为包装在一起以实现信息就隐藏。封装实际上使用方法(method)将类的数据隐藏起来,控制用户对类的修改和访问数据的程度,从而带来模块化(Modularity)和信息隐藏(Information hiding)的好处;接口(interface)是封装的准确描述手段。 Dog类通过使用类和访问控制(private,public)隐藏了属性color,开放了接口setColor(),getColor(),bark()和toString。
  • 示例:
    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());
	}
    }
继承
  • 以封装为基础,一个类的定义可以基于另外一个已经存在的类,即子类基于父类,从而实现父类代码的重用。其更为广泛而重要的作用是实现多态。
  • 示例:Dog类和Cat类都有Color属性和相应的setter和getter方法,可以通过继承使其精炼化,把Color属性和相应的setter和getter方法放到父类Animal中。
    public abstract class Animal {
        private String color;
        public String getColor() {
            return color;
        }
        public void setColor(String color) {
            this.color = color;
        }
        public abstract String shout(); 
    }
    public class Dog extends Animal{
        public String shout(){
            return "汪汪";
        }
        public String toString(){
            return "The Dog's color is " + this.getColor() +", and it shouts "+ this.shout() + "!";
        }
    }
    public class Cat extends Animal{
        public String shout(){
            return "喵喵";
        }
        public String toString(){
            return "The Cat's color is " + this.getColor() +", and it shouts "+ this.shout() + "!";
        }
    }
多态
  • 在Java中,当我们用父类声明引用,用子类生成对象时,多态就出现了

设计模式初步

S.O.L.I.D原则

  • SRP(Single Responsibility Principle,单一职责原则)
    • 决不要有一个以上的理由修改一个类。对象提供单一职责的高度封装,对象的改变仅仅依赖于单一职责的改变。
  • OCP(Open-Closed Principle,开放-封闭原则)
    • 软件实体(类,模块,函数等)应该对扩充开放,对修改封闭。
    • 对扩充开放,要求软件模块的行为必须是可以扩充的,在应用需求改变或需要满足新的应用需求时,我们要让模块以不同的方式工作;
    • 对修改封闭,要求模块的源代码是不可改动的,任何人都不许修改已有模块的源代码。
    • OCP的实现手段:(1)抽象和继承,(2)面向接口编程。
  • LSP(Liskov Substitusion Principle,Liskov替换原则)
    • 子类必须可以被其基类所代;使用指向基类的指针或引用的函数,必须能够在不知道具体派生类对象类型的情况下使用它。
    • 核心思想是父类型对象可以被子类型对象所取代。
  • ISP(Interface Segregation Principle,接口分离原则)
    • 客户不应该依赖他们并未使用的接口(接口的功能不要太多)
  • DIP(Dependency Inversion Principle,依赖倒置原则)
    • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。

模式与设计模式

  • 模式是某外在环境下﹐对特定问题的惯用解决方法,可以看作对一个问题可复用的专家级解决方法。

设计模式实示例

  • 四个基本要素:
    • 模式名(Pattern name):描述模式,便于交流,存档
    • 问题(Pattern name):描述何处应用该模式
    • 解决(Solution):描述一个设计的组成元素,不针对特例
    • 结果(Consequence):应用该模式的结果和权衡
  • 示例:
class Integer { 
   int value;    
   public Integer(){
      value=100;  
   }    
   public void DisplayValue(){
        System.out.println(value);  
   } 
} 
class Document { 
   Integer pi; 
   public Document(){
       pi = new Integer(); 
   } 
   public void DisplayData(){
      pi.DisplayValue();  
   } 
} 
public class MyDoc{ 
   static Document d;
   public static void main(String [] args) { 
        d = new Document(); 
        d.DisplayData(); 
   } 
}
  • 运行结果:

练习

使用TDD的方式设计关实现复数类Complex

伪代码

1)复数类ComplexNumber的属性
  realPart: 实部,代表复数的实数部分
  imaginPart: 虚部,代表复数的虚数部分
2)复数类ComplexNumber的方法
  ComplexNumber() 构造函数,将实部,虚部都置为0
  ComplexNumber(double realPart, double imaginPart) 构造函数,创建复数对象的同时完成复数的实部,虚部的初始化
  getRealPart() 获取实部
  getImaginaryPart() 获取虚部
  getRealPart(double realPart) 设置实部
  getImaginaryPart(double imaginPart) 设置虚部
  ComplexAdd(ComplexNumber c)    复数相加
  ComplexAdd(double realPart2)   复数相加
  ComplexMinus(ComplexNumber c)  复数相减
  ComplexMinus(double realPart2) 复数相减
  ComplexMulti(ComplexNumber c)  复数相乘
  ComplexMulti(double realPart2) 复数相乘
  toString() 把当前复数对象的实部,虚部组合成a+bi的字符串形式

测试代码

import static junit.framework.Assert.*;
import junit.framework.TestCase;
import org.junit.Test;

public class ComplexNumberTest extends TestCase {
    ComplexNumber c1 = new ComplexNumber(3,5);
    ComplexNumber c2 = new ComplexNumber(3,5);
    double a = 5;
    @Test
    public void testAdd1() throws Exception {
        c1.ComplexAdd(c2);
        assertEquals(6.0, c1.getRealPart());
        assertEquals(10.0, c1.getImaginPart());
    }
    @Test
    public void testAdd2() throws Exception {
        c1.ComplexAdd(a);
        assertEquals(8.0, c1.getRealPart());
        assertEquals(5.0, c1.getImaginPart());
    }
    @Test
    public void testMinus1() throws Exception {
        c1.ComplexMinus(c2);
        assertEquals(0.0, c1.getRealPart());
        assertEquals(0.0, c1.getImaginPart());
    }
    public void testMinus2() throws Exception {
        c1.ComplexMinus(a);
        assertEquals(-2.0, c1.getRealPart());
        assertEquals(5.0, c1.getImaginPart());
    }
    @Test
    public void testMulti1() throws Exception {
        c1.ComplexMulti(c2);
        assertEquals(9.0, c1.getRealPart());
        assertEquals(25.0, c1.getImaginPart());
    }
    public void testMulti2() throws Exception {
        c1.ComplexMulti(a);
        assertEquals(15.0, c1.getRealPart());
        assertEquals(5.0, c1.getImaginPart());
    }
}

产品代码

public class ComplexNumber {
    private double realPart;
    private double imaginPart;
    public ComplexNumber(){
        this.realPart = 0.0.;
        this.imaginPart = 0.0;
    }
    public ComplexNumber(double r, double i){
        this.realPart = r;
        this.imaginPart = i;
    }
    public double getRealPart(){
        return realPart;
    }
    public double getImaginPart(){
        return imaginPart;
    }
    public void setRealPart(double d){
        this.realPart = d;
    }
    public void setImaginPart(double d){
        this.imaginPart = d;
    }
    public void ComplexAdd(ComplexNumber c){
        this.realPart += c.realPart;
        this.imaginPart += c.imaginPart;
    }
    public void ComplexAdd(double c){
        this.realPart += c;
    }
    public void ComplexMinus(ComplexNumber c){
        this.realPart -= c.realPart;
        this.imaginPart -= c.imaginPart;
    }
    public void ComplexMinus(double c){
        this.realPart -= c;
    }
    public void ComplexMulti(ComplexNumber c){
        this.realPart *= c.realPart;
        this.imaginPart *= c.imaginPart;
    }
    public void ComplexMulti(double c){
        this.realPart *= c;
    }
    @Override
    public String toString(){
        return String.format("%f + %fi", this.getRealPart(),this.getImaginPart());
    }
}

测试代码运行结果:

UML建模

  • UML是一种通用的建模语言

  • 打开WhiteStarUML

  • 选择已经写好的.java文件,导入

  • 最后显示如下,很清楚地展现了每个类的成员变量、方法函数:

  • 一般规则:

    • +表示public
    • #表示 protected
    • -表示 private
    • 如果类A中a()方法是抽象方法,那么a()是斜体的,类A是抽象类,所以也是斜体的
    • UML类图要展示类之间的静态关系,UML中依赖用带箭头的直线表示,例如上例中的Animal类依赖Cat类和Dog类
    • UML类图中继承的表示法,是用一个带三角的直线指向父类,例如上例中Cat类和Dog类继承Animal类,消除了Dog类和Cat类中的重复代码,符合DRY的要求

遇到问题及解决办法

  • 练习部分内容与上次实验类似,可以在上次的基础上稍加修改。
  • 问题一:最开始不会使用UML,然后通过查询百度,参考他人博客,了解了UML大致使用方法,首先UML可以将一个产品中不同成员变量、不同方法函数通过图表的形式清楚地展现出来,还可以通过“带箭头的直线”和“带三角的直线”构建不同类之间的依赖关系和继承关系。
  • 问题二:在写复数的测试类时,开始IDEA不能正确识别其中的部分代码(标红),同学也遇到了相同的问题,最后参照老师的博客,加入以下代码即可:
import junit.framework.TestCase;
import org.junit.Test;
  • 问题三:在写复数类测试类时,有一句是这样写的:assertEquals(0, c1.getRealPart());,随即运行的时候编译器报错了,显示这个测试类没有通过。后来改为assertEquals(0.0, c1.getRealPart());才测试通过,因为0会默认为整型,而整型的数是无法与double型的数进行直接比较大小的。

总结

  • Junit单元测试,在实验一为了验证代码的可靠性,我自己手动建立了一个测试类,通过返回值在屏幕上输出,来判断代码是否可靠,这样有几个弊端,第一手动建立麻烦,第二你的这个类到底是不是这个类的测试类,在代码复杂的情况下容易混淆。

  • 所以在本周老师讲述了一种新的测试方法——Junit单元测试,首先通过go to可以自动建立一个对应的测试类,在这个类中可以增加许多测试用例,测试用例的名字可以随便起,但是在这个方法之前一定要标注@Test。进而通过assertEquals()函数来比较理论值和实际值是否有偏差,达到测试的目的。运行该测试类,IDEA会自动判断,并告诉你通过了哪些测试用例,失败了哪些测试用例。

  • 同时,我们在建立测试类时还应该注意,测试的例子要恰当,要能全面的验证这个类的所用功能,特别要主要边界值的检验,使得测试结果过更有说服力。

  • PSP(Personal Software Process)时间:

步骤 耗时 百分比
需求分析 10min 8.3%
设计 20min 16.7%
代码实现 30min 25.0%
测试 45min 37.5%
分析总结 15min 12.5%

参考资料

原文地址:https://www.cnblogs.com/20145221GQ/p/5401463.html