Loading

【助教】什么是封装

第一阶段目标
能把计算的功能封装起来,通过测试程序和API 接口测试其简单的加法功能。

我们试着来解决这个问题:
提到封装,我想说一下什么是:没有封装。我找了一个童鞋写的没有封装的例子大家看下:

import java.util.Scanner;

public class Calculator {
	public static void main(String[] args) {
		System.out.println("运算符是 +"); 
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入第一个数:");
		String aStr = sc.nextLine();
		System.out.println("请输入第二个数:");
		String bStr = sc.nextLine();
		System.out.println("请输入运算符:");
		String cc = sc.nextLine();
		double a = Double.parseDouble(aStr);
		double b = Double.valueOf(bStr);

		if (cc.equals("+")) {
			System.out.println(a + b);
		} else {
			System.out.println("不好意思,我只能支持加法运算");
		}

	}
}

这个程序很简单:输入两个操作数,输入一个操作符(目前支持加法),输出结果。

但是,面对这样的程序,我们可能会遇到以下问题:
1.如何做测试?
2.假如老(客)师(户)"得寸进尺"地提出要扩展一下,比如说我不仅要加法,还要支持两个数的减法,乘法,除法,你该怎么做?
3.如何测试扩展完以后的程序?

对于第一个问题,大多数人可能想到的测试方法就是:运行一下这个程序,输入几组数据,然后看运行结果。
第二个问题,有以下两种方法,童鞋们可以试着对比一下:
方案一:简单粗暴main方法加if/else

import java.util.Scanner;

public class Calculator {
	public static void main(String[] args) {
		System.out.println("运算符是 +,-,*,/");
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入第一个数:");
		String aStr = sc.nextLine();
		System.out.println("请输入第二个数:");
		String bStr = sc.nextLine();
		System.out.println("请输入运算符:");
		String cc = sc.nextLine();
		double a = Double.parseDouble(aStr);
		double b = Double.valueOf(bStr);
		if (cc.equals("+")) {
			System.out.println(a + b);
		} else if (cc.equals("-")) {
			System.out.println(a - b);
		} else if (cc.equals("*")) {
			System.out.println(a * b);
		} else if (cc.equals("/")) {
			if (b != 0) {
				System.out.println(a / b);
			} else {
				System.out.println("分母不能为零!");
			}
		} else {
			System.out.println("输入不符合要求!");
		}
	}
}

方案二: 将计算的逻辑代码封装到一个类里面,main方法直接调用就可以
Calculator.java



import java.util.Scanner;


public class Calculator {
	public static void main(String[] args) {
		System.out.println("运算符是 +,-,*,/");
		Scanner sc = new Scanner(System.in);
		System.out.println("请输入第一个数:");
		String aStr = sc.nextLine();
		System.out.println("请输入第二个数:");
		String bStr = sc.nextLine();
		System.out.println("请输入运算符:");
		String cc = sc.nextLine();
		double a = Double.parseDouble(aStr);
		double b = Double.valueOf(bStr);
		
		Core core = new Core();
		core.calc(cc, a, b);
	}
}

Core.java


public class Core {
	public void calc(String cc, double a, double b) {
		if (cc.equals("+")) {
			System.out.println(a + b);
		} else if (cc.equals("-")) {
			System.out.println(a - b);
		} else if (cc.equals("*")) {
			System.out.println(a * b);
		} else if (cc.equals("/")) {
			if (b != 0) {
				System.out.println(a / b);
			} else {
				System.out.println("分母不能为零!");
			}
		} else {
			System.out.println("输入不符合要求!");
		}
	}
}

方案二就是传说中的封装:

Core core = new Core();
core.calc(cc, a, b);

这样有什么好处呢?
我们看第三个问题:

如何测试扩展完以后的程序?

如果用方案1的话,你又要运行一下,手动输入更多的几组数据来看结果,可是,如果用方案二的话,我们针对计算这部分的逻辑,可以只对Core这个类的calc方法做单元测试,而且对于之前做过的单元测试可以复用下来,不需要你再像方案一一样多次运行输入数据看结果这种费时间又可能测试情况覆盖不全,同时,你在增加calc的功能的时候,不需要改main方法。
所以,总结一下,我认为,封装有以下几个好处:
1.复用之前的测试用例。
2.维护代码方便。
3.扩展方便。
4.将逻辑代码和 UI 代码分离,多种表现层共享同一个业务逻辑模块。

好像我们不知不觉就把老师的第一个要求和第二个要求完成了:
第一阶段目标 - 能把计算的功能封装起来,通过测试程序和API 接口测试其简单的加法功能。
第二阶段目标 - 通过测试程序和API 接口测试其简单的加减乘除功能。并能看到代码覆盖率。

提示:童鞋们要做的就是把自己的之前两次编程作业中的计算的逻辑,封装到自己的Core类里面,能力强的童鞋,calc方法也可以自己设计,考虑用一些设计模式,那就更赞啦!
邹老师提供了一个更加好的设计方法:calc 函数接受字符串的输入,然后通过解析字符串来获得计算结果,比如:

Core.calc(“1 + 1”) ;
Core.calc(“3 - 1”) ;
...

此时,老(客)师(户)提出更加“惨绝人寰”的要求:
第三阶段目标 - 通过测试程序和API 接口测试对于各种参数的支持。并能看到代码覆盖率。
假如我们用了封装,就很好扩展这个需求,最简单的解决办法,只需要在Core.java中增加一个重载的方法:

public class Core {
	static final int NUM_MAX_OPERATOR = 1; // 最多4 个运算符
	static final int NUM_SCALE = 2; // 数值范围是 -1000 到 1000
	static final int NUM_PRECISION = 3; // 精度是小数点后两位
	public  double calc(String cc) {
	    double result = 0.0d;
		// 这个方法中我可以计算两个数的+ - * /
		return result;
	}
	
	
	public double calc(String calStr, int precision) {
	    double result = 0.0d;
		if (NUM_MAX_OPERATOR == precision) {
			// 这里我可以处理最多四个运算符的字符串calStr哦:)
		} else if (NUM_SCALE == precision) {
			// 这里我可以处理数值范围是 -1000 到 1000的字符串calStr哦:)
		} else if (NUM_PRECISION == precision) {
			// 这里我可以处理精度是小数点后两位的字符串calStr哦:)
		}
		return result;
	}
}

针对calc做单元测试也很方便,不会像之前一样把所有逻辑都写在main方法里面导致main方法就难以维护了。

提示:对于配置这里,邹老师提供了更多的方式:比如,单独增加一个setting方法来控制不一样的情况,或者不像我这样用一个int类型的数来标明,可以用一个xml格式的值来控制不一样的情况,同时,处理多个情况的时候,也不一定要像我这样 else/if,试着用一点模式。
PS:做的时候,如果童鞋是传入字符串在处理,会发现很多算法上的问题,基础好的童鞋可以研究,暂时想不出的童鞋可以考虑先用简单的方式处理。我大学的算法和数据结构基础不太好,也在刻苦补充中,对算法有兴趣的童鞋可以拿邹老师的《编程之美》来看看。

老(客)师(户)最后一个要求是:
第四阶段目标 - 通过增量修改改进程序, 完成对各种错误情况的处理。

对于这个问题:
参见我之前的两篇单元测试的博客,其中有讲到异常处理的情况:
http://www.cnblogs.com/greyzeng/p/4439080.html
http://www.cnblogs.com/greyzeng/p/4443160.html

多说几句:
用了“惨绝人寰” ,“得寸进尺”这样的词来形容老(客)师(户),其实是深有感触的,工作中,给客户做的系统,可能会频繁的变更需求,一下子说做这个需求,一下子说那个地方要改。导致我们这些开发人员苦不堪言。
相比之下:老师其实最好了,每次作业的需求那么的明确,不会今天布置一个单元测试的作业,明天说要改成程序设计的作业,所以,我们还是很幸福的,大家好好珍惜大学的锻炼机会,加油!

posted @ 2015-04-29 10:21  Grey Zeng  阅读(553)  评论(2编辑  收藏  举报