一句意味深长的问句:你还在用if-else吗?
上个星期,被训了,“你自己看下你写的代码,这样写会死人的,到时候怎么维护,我靠。如果有100个类别,你不是要写100个if-else”。唉,只能怪自己木有太多经验,于是我便想该如何重构我的冗余代码。吃一堑长一智,以后避免再犯那种低级错误。

星期天查了下资料,先整理如下:问题说白了就是用什么样的设计来替代冗余的if-else代码结构。编程一个很重要的原则就是开闭原则,何为开闭原则,就是系统扩展开放,系统修改关闭。也就是说,当有新的需求或者需求有变更时,我们应该做的扩展新的功能重新编码而不是去修改以前写好的功能代码,这就是开闭原则。我记得很清楚,是因为我刚毕业时一次面试没过就是栽在那道题上。
首先我们模仿影片租赁过程,顾客租凭影片,影片分为儿童片、普通片、新片。根据影片类型及租凭天数价格各不相同,优惠程度不同,用户累计积分不同。
我先用常用的if-else写下实现:
public class Movie {
public static int NORMAL = 1; // 普通片
public static int NEW_RELEASE = 2; // 最新片
public static int CHILDREN = 3; // 儿童片
/**
* 获取租赁影片总价
*
* @param movieType
* 影片类型
* @param days
* 租赁天数
* @throws MovieException
*/
public double getCharge(int movieCode, int days) throws MovieException {
double result = 0;
// 普通片
if (movieCode == Movie.NORMAL) {
result = 2;
// 如果租赁天数大于2则,则优惠
if (days > 2) {
result += (days - 2) * 1.5;
}
// 返回总价
return result;
}
// 最新发布片
else if (movieCode == Movie.NEW_RELEASE) {
// 新片没有优惠,单价为3
return days * 3;
}
// 儿童片
else if (movieCode == Movie.CHILDREN) {
// 影片单价
result = 1.5;
// 如果租赁时间大于3天则价格优惠
if (days > 3) {
result += (days - 3) * 1.5;
}
// 返回租赁影片总价
return result;
} else {
throw new MovieException("影片不存在 ");
}
}
/**
* 获取租赁影片积分
*/
public double getIntegral(int movieCode, int days) throws MovieException {
// 普通片
if (movieCode == Movie.NORMAL){
return days * 2;
}
// 最新发布片
else if (movieCode == Movie.NEW_RELEASE){
return days * 3;
}
// 儿童片
else if (movieCode == Movie.CHILDREN)
return days * 1.5;
else{
throw new MovieException(" 影片不存在 ");
}
}
}
在需求不改变的情况下,这段代码完全符合需求,但是如果我现在要求添加一种影片类型:动作片,那我们的getCharge()和getIntegral()方法不要相应加上新影片类型的else-if判断分支,如果只是添加一种影片类型,这个工作量并不大,如果我要增加10种,100种呢,那这个if-else结构就很壮观了。就算只是添加一种类型,从开闭原则角度去考虑,它还是违背了开闭原则,所以就决定了她不是一个好的代码设计。其实懂点OOP的程序员都知道,这段代码没有体现OO的精髓,本质是面向过程,没有一丁点OO的东西在里面。想当初我就是从写这种代码开始进入Java世界的,现在连我都要开始鄙视当初的自己。
现在我们来用OO的思想来重新组织我们的代码,完全摒弃掉if-else.我们首先要思考先前面向过程的实现代码,租赁影片的价格计算和用户积分计算都是一一判断影片类型然后分别处理,如果有100个类型,我们就可能会有100个判断处理分支,这时候我们就要考虑抽象共同点,不难发现,不管是那种类型影片,你租赁影片就要计算租赁总结和通过租赁获得的积分,具体各种类型影片的计算方式,由各自的子类去具体实现。这样就不用考虑影片类型对计算方式的影响。思路明白了,那就开始编码重新实现。
首先是定义个影片类型的基类,他有计算租赁总价和计算用户积分的两个方法,由于具体计算方式已经跟影片类型解耦了,所以方法参数只需要一个租赁天数。MovieException是我自定义的一个异常类,稍候在文章最后代码会贴出。
/**
* 影片类型基类
* @author Lanxiaowei
* @createTime 2011-7-11 上午12:16:25
*/
public abstract class MovieType {
public abstract double getCharge(int days) throws MovieException;
public abstract double getIntegral(int days) throws MovieException;
}
然后就3个影片类型子类,和其具体实现,细节请阅读代码,就不解释了:
//普通影片
public class NormalMovie extends MovieType {
@Override
public double getCharge(int days) throws MovieException {
double result = 2;
// 如果租赁天数大于2则,则优惠
if (days > 2) {
result += (days - 2) * 1.5;
}
// 返回总价
return result;
}
@Override
public double getIntegral(int days) throws MovieException {
return days * 2;
}
}
//最新上映影片
public class NewReleaseMovie extends MovieType{
@Override
public double getCharge(int days) throws MovieException {
return days * 3;
}
@Override
public double getIntegral(int days) throws MovieException {
return days * 3;
}
}
//儿童片
public class ChildrenMovie extends MovieType {
@Override
public double getCharge(int days) throws MovieException {
double result = 1.5;
// 如果租赁时间大于3天则做价格优惠
if (days > 3) {
result += (days - 3) * 1.5;
}
// 返回租赁影片总价
return result;
}
@Override
public double getIntegral(int days) throws MovieException {
return days * 1.5;
}
}
子类具体实现都编写好了,然后就是根据用户实际租赁的影片类型创建相应影片并设置影片类型,所以下一步就是编写影片类,影片类应该有一个影片类型的引用,因为事先我们不可能知道用户会租赁什么类型影片,所以影片类拥有的应该是一个影片类型的基类类型引用,实际计算租赁价格时根据实际租赁影片类型去创建实际影片类型对象赋给父类引用,这里就要到了java多态,现在问题回到了:影片类该如何得到影片类型实例呢,我们不难想到:用简单对象工厂。工厂方法接收一个影片类型然后根据Java反射机制去动态创建实例,好了,思路清晰了,那就开始编码实现:
//影片类
public class Movie {
private MovieType type; //影片类型
public MovieType getType() {
return type;
}
public void setType(Class movieType) throws MovieException {
try {
this.type = (MovieType)movieType.newInstance();
} catch (Exception e) {
throw new MovieException("影片不存在");
}
}
/**
* 自定义异常类
* @author Lanxiaowei
* @createTime 2011-7-11 上午12:01:51
*/
public class MovieException extends Exception {
public MovieException(){
super();
}
public MovieException(String message){
super(message);
}
}
}
下面赶紧编写个测试类测试我们的设计是否是正确的:
/**
* @author Lanxiaowei
* @createTime 2011-7-11 上午12:37:52
*/
public class MovieTest {
public static void main(String[] args) {
Movie normalMovie = new Movie();
Movie newMovie = new Movie();
Movie childrenMovie = new Movie();
try {
normalMovie.setType(NormalMovie.class);
newMovie.setType(NewReleaseMovie.class);
childrenMovie.setType(ChildrenMovie.class);
System.out.println(normalMovie.getType().getCharge(10));
System.out.println(normalMovie.getType().getIntegral(10));
System.out.println(newMovie.getType().getCharge(10));
System.out.println(newMovie.getType().getIntegral(10));
System.out.println(childrenMovie.getType().getCharge(10));
System.out.println(childrenMovie.getType().getIntegral(10));
} catch (MovieException e) {
System.out.println("影片不存在");
}
}
}
到此我们的if-else替换方案测试成功。以后如果要添加一个新的影片类型,只需要添加个影片类型子类,并编写相应的租赁总价和积分具体实现,而不用修改原来的子类实现,完全符合开闭原则,但是我们这个设计还是有BUG的,如果我们的价格和积分统计规则发生改变呢,那我们的所有子类的实现都要进行修改,但是这个不在我们今天的讨论范围,我的主题是if-else替代方案,这个BUG留做下个主题再解决。
什么框架不重要,不能被框架所迷惑,学习框架的实现思想才是最重点,不然就不是你在用框架,而是被框架所用。写代码不是目的,目的是从中总结经验养成一种良好的代码习惯,习惯积累久了就形成了所谓的“思想”。
本文来源 我爱IT技术网 http://www.52ij.com/jishu/114.html 转载请保留链接。
- 评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)

好好啊!!
厉害,好评!!
思维深邃,道理很深,颇有收货,理解其中的内涵了。