JAVA随笔

 

使用state pattern替代if else

大多数开发人员现在还在使用if else的过程结构,曾看过jdonbanq大哥写的一篇文章,利用command,aop模式替代if else过程结构。当时还不太明白,这几天看了《重构》第一章的影片租赁案例,感触颇深。下面我来谈一谈为什么要用state pattern替代if else,替代if else有什么好处,以及给出详细代码怎么替代if else。本文参考jdon的“你还在使用if else吗?”及《重构》第一章。

 

首先我们模仿影片租赁过程,顾客租凭影片,影片分为儿童片、普通片、新片。根据影片类型及租凭天数价格各不相同(优惠程度不同),用户累计积分不同。

 

OK ,现在我们使用 if else 表示。

package  com.qujingbo.movie;

/**
 * <p/> Title:影片基类
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 15:47:55
 * </p>
 * 
 * 
@author  EOMS 曲静波
 * 
@version  1.0
 
*/

public   class  Movie  {

     
//  普通片标识
      public   static   int  REGULAR  =   1 ;
    
     
//  新片标识
      public   static   int  NEW_RELEASE  =   2 ;
    
     
//  儿童片标识
      public   static   int  CHILDREN  =   3 ;
    
     
/**
     * 获取租赁影片总价
     *
     * 
@param  movieCode
     * 影片类型
     * 
@param  days
     * 租凭天数
     * 
@return  租赁影片总价
     * 
@throws  MovieException
     * 没有影片类型抛出异常
     
*/

     
public   double  getCharge( int  movieCode,  int  days)  throws  MovieException  {
     
double  result  =   0 ;
     
//  普通片
      if  (movieCode  ==  Movie.REGULAR)
     
//  单价为2
      {
     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( " 影片不存在 " );
     }

    
     
/**
     * 获取租赁影片积分
     *
     * 
@param  movieCode
     * 影片类型
     * 
@param  days
     * 租凭天数
     * 
@return  租赁影片积分
     * 
@throws  MovieException
     * 没有影片类型抛出异常
     
*/

     
public   double  getIntegral( int  movieCode,  int  days)  throws  MovieException
     
{
     
//  普通片
      if  (movieCode  ==  Movie.REGULAR)
     
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( " 影片不存在 " );
    
        }

}


OK ,我们看一下,现在的 Movie 完全符合租赁需求,通过 getIntegral(int movieCode,int days) getCharge(int movieCode,int days) 来获得租赁积分及租赁价格。从开闭原则角度来看,如果要添加新的影片类型,我们必须修改 getIntegral(int movieCode,int days) getCharge(int movieCode,int days) 这两个方法。而若要改变租赁价格、积分的优惠规则时,仍需要修改 getIntegral(int movieCode,int days) getCharge(int movieCode,int days) 方法。现在看来,只有三种影片类型,维护还较方便。而当影片类型较多时,例如 10 种, 100 种影片类型,这样就是不可以想像的维护。

 

现在我们来看一下,使用 state pattern 来代替 if else 。先来个类图。

 

ifelse.jpg 

首先我们建立一个 abstract class Price 做为影片类型的基类,基类中含有两个 abstract 方法,获取总价格 getCharge(int days), 获取总积分 getIntegral(int days) 方法 , 继承 abstract classPrice 的三个影片类型儿童片 class ChilerenPrice, 普通片 class RegularPrice, 最新片 class NewReleasePrice 。分别实现 getCharge(int days),getIntegral(int days) 方法,实现方法写入计算价格的优惠方案及积分的方案。当需要修改方案时,我们只需在某个影片类的方法中对应修改就可以。若新增一个影片分类时,我们只需新增一个实现类实现 abstract class Price 类就 OK

 

class Movie 代表影片,其关联一个 Price 类,而 setPrice(String movieClass) 方法类似于一个工厂类,传入 movieClass 为包名类名,用 java 反射机制实例化一个具体传入 movieClass 的影片类型实现类,这样我们通过这几行代码就可以获得该影片类型的价格和积分。

Movie regularMovie  =   new  Movie();
regularMovie.setPrice(Movie.REGULAR);
System.out.println(
" 普通影片租赁10天的价格 " +  regularMovie.getPrice().getCharge( 10 ));
System.out.println(
" 普通影片租赁10天的积分 " +  regularMovie.getPrice().getIntegral( 10 ));

下面我们给出详细代码

abstract class Price价格基类

package com.qujingbo.movie;

/**
 * <p/> Title:
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 15:48:22
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public abstract class Price {

    
/**
     * 获取租赁影片价格需实现该此方法
     * 
     * 
@param days
     *            租赁天数
     * 
@return 返回影片价格
     
*/

    
public abstract double getCharge(int days);

    
/**
     * 获取租赁影片积分需实现此方法
     * 
     * 
@param days
     *            租赁天数
     * 
@return 返回影片积分
     
*/

    
public abstract double getIntegral(int days);

}



儿童片ChildrenPrice类,实现abstract class Price ,实现儿童片租赁总价getCharge(int days)及儿童片租赁积分getIntegral(int days)
package com.qujingbo.movie;

/**
 * <p/> Title:儿童片租赁积分、价格实现
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 15:49:04
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public class ChildrenPrice extends Price {

    
/**
     * 儿童片返回租赁积分,儿童片积分规则为: 根据
     
*/

    
public double getIntegral(int days) {
        
// 返回租赁影片积分
        return days * 1.5;
    }


    
/**
     * 儿童片返回租赁价格
     
*/

    
public double getCharge(int days) {
        
// 影片单价
        double result = 1.5;
        
// 如果租赁时间大于3天则做价格优惠
        if (days > 3{
            result 
+= (days - 3* 1.5;
        }

        
// 返回租赁影片总价
        return result;
    }


}



普通片RegularlPrice类,实现abstract class Price ,实现普通片租赁总价getCharge(int days)及普通片租赁积分getIntegral(int days)

package com.qujingbo.movie;

/**
 * <p/> Title:普通片租赁积分、价格实现
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 15:50:10
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public class RegularlPrice extends Price {
    
/**
     * 普通片返回租赁积分,普通片积分规则
     
*/

    
public double getIntegral(int days) {
        
// 返回租赁影片积分
        return days * 2;
    }


    
/**
     * 普通片返回租赁价格
     
*/

    
public double getCharge(int days) {
        
// 单价为2
        double result = 2;
        
// 如果租赁天数大于2则,则优惠
        if (days > 2{
            result 
+= (days - 2* 1.5;
        }

        
// 返回总价
        return result;
    }


}



最新发布片
NewReleasePrice类,实现abstract class Price ,实现最新发布片租赁总价getCharge(int days)及最新发布片租赁积分getIntegral(int days)

package com.qujingbo.movie;

/**
 * <p/> Title:最新发布片租赁积分、价格实现
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 15:48:51
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public class NewReleasePrice extends Price {
    
/**
     * 最新发布片返回租赁积分,最新发布片积分规则
     
*/

    
public double getIntegral(int days) {
        
// 返回租赁影片积分
        return days * 3;
    }


    
/**
     * 最新发布片返回租赁价格
     
*/

    
public double getCharge(int days) {
        
// 新片没有优惠,单价为3
        return days * 3;
    }


}



电影Movie类,setPrice(String movieClass)(工厂)方法,通过java反射机制实现movieClass(包名,类名)类。若没有movieClass这个类,则抛出MovieException异常。

package com.qujingbo.movie;

/**
 * <p/> Title:影片类
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 15:47:55
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public class Movie {
    
// 普通片标识
    public static String REGULAR = "com.qujingbo.movie.RegularlPrice";

    
// 新片标识
    public static String NEW_RELEASE = "com.qujingbo.movie.NewReleasePrice";

    
// 儿童片标识
    public static String CHILDREN = "com.qujingbo.movie.ChildrenPrice";

    
private Price price;

    
public Price getPrice() {
        
return price;
    }


    
/**
     * 确定返回具体某个影片类型的实现类,有点像工厂
     * 
     * 
@param movieCode
     *            影片类型
     * 
@throws MovieException
     *             若无影片类型则抛异常。
     
*/

    
public void setPrice(String movieClass) throws MovieException {
        
try {
            Class cls 
= Class.forName(movieClass);
            
this.price = (Price) cls.newInstance();
        }
 catch (Exception e) {
            
throw new MovieException("影片不存在");
        }

    }

}



给出MovieException源码。

package com.qujingbo.movie;

/**
 * <p/> Title:自定义异常
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 19:21:08
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public class MovieException extends Exception {
    
public MovieException(String msg) {
        
super(msg);
    }

}

下面模访一个顾客租赁影片。

package com.qujingbo.movie;

/**
 * <p/> Title:
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 19:26:23
 * </p>
 * 
 * 
@author EOMS 曲静波
 * 
@version 1.0
 
*/

public class Customer {
    
/**
     * 消费(测试程序)
     * 
     * 
@throws MovieException
     *             若没有影片,抛出异常
     
*/

    
public void consume() throws MovieException {
        
// 普通电影
        Movie regularMovie = new Movie();
        regularMovie.setPrice(Movie.REGULAR);
        
// 最新发布电影
        Movie newReleaseMovie = new Movie();
        newReleaseMovie.setPrice(Movie.NEW_RELEASE);
        
// 儿童电影
        Movie childrenMovie = new Movie();
        childrenMovie.setPrice(Movie.CHILDREN);

        System.out.println(
"普通影片租赁10天的价格"
                
+ regularMovie.getPrice().getCharge(10));
        System.out.println(
"最新影片租赁10天的价格"
                
+ newReleaseMovie.getPrice().getCharge(10));
        System.out.println(
"儿童影片租赁10天的价格"
                
+ childrenMovie.getPrice().getCharge(10));
        
        System.out.println(
"普通影片租赁10天的积分"
                
+ regularMovie.getPrice().getIntegral(10));
        System.out.println(
"最新影片租赁10天的积分"
                
+ newReleaseMovie.getPrice().getIntegral(10));
        System.out.println(
"儿童影片租赁10天的积分"
                
+ childrenMovie.getPrice().getIntegral(10));
        
        
    }

}


写一 junit 测试类运行 class Customer consume() 方法。

package  com.qujingbo.movie;

import  junit.framework.TestCase;

/**
 * <p/> Title:junit测试类
 * </p>
 * <p/> Description:
 * </p>
 * <p/> Date:2006-10-14 19:32:57
 * </p>
 * 
 * 
@author  EOMS 曲静波
 * 
@version  1.0
 
*/

public   class  CustomerTest  extends  TestCase  {

    
private  Customer customer  =   null ;

    
protected   void  setUp()  throws  Exception  {
        
super .setUp();
        customer 
=   new  Customer();
    }


    
protected   void  tearDown()  throws  Exception  {
        
super .tearDown();
    }


    
/*
     * Test method for 'com.qujingbo.movie.Customer.consume()'
     
*/

    
public   void  testConsume()  {
        
try   {
            customer.consume();
        }
  catch  (MovieException e)  {
            System.out.println(
" 没有该类影片 " );
        }

    }


}


OK 。结果为:

普通影片租赁 10 天的价格 14.0

最新影片租赁 10 天的价格 30.0

儿童影片租赁 10 天的价格 12.0

普通影片租赁 10 天的积分 20.0

最新影片租赁 10 天的积分 30.0

儿童影片租赁 10 天的积分 15.0

 

最后我要说,我们用 OO 表示的租赁过程并不完整,因为顾客不一定只租赁一部影片,而要租赁多部影片,这样我们缺少一个 Rental (租赁类)。而只是为说明 state pattern 替代 if else ,所以我们没有添加 Rental (租赁类),若需要参考,请查阅《重构》第一章。 点击下载源码.

 

这是我第一次写技术文章,如果有适当的地方,请各位朋友提出各自见解。

 

email:qujingbo@gmail.com

posted on 2006-10-15 00:10 曲静波 阅读(3886) 评论(22)  编辑  收藏 所属分类: design pattern

评论

# re: 使用state pattern替代if else 2006-10-15 10:28 马嘉楠

学习
不错,写得很好!  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 10:40 曲静波

谢谢支持,我一定会再接再厉!也请有疑义的地方提出来,共同讨论.  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 10:41 zeroblue

so good!  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 10:54 zeroblue

嗯,问下,如果使用反射机制,Movie类是否不用声明静态变量。
否则增加新的影片类型,不是还要修改Movie类。
如果要声明的话,是否加上final比较好?  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 10:57 黄奕福

思路不错,感觉就是java的风格,一句话的事情要绕来绕去将pattern,容易搞晕了,还要给生手解释半天.

有时候还不如if来得直接了当,符合常理思维,谁都看得明白.呵呵  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 11:06 zeroblue

还是应该pattern吧,这里是以影片类型举例,可以想象一个大型的影片公司,影片类型肯定非常多,都在一个类里用if else搞,头可能也比较晕。
而且类型价格等东西有变更,或减增。维护起来会死人的。  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 13:12 kebo

你这个实例化合适的状态类是在什么时候啊?
如果根据客户端的选择的话,你还是得if else判断???  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 15:10 曲静波

@zeroblue


嗯,你说的有道理.final应该是加的,我露掉了,不好意思.不过想想的话,如果不定义常量的话,也有很多问题,例如:将来修改类名,或多个地方引用,或手敲类名出错都是问题.这就看个人看法吧.如果不加常量的话,应该完全符合开闭原则了.你有什么更好的想法吗?在既符合开闭原则,又符合上面提到的问题.  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 15:13 曲静波

@kebo
如果客户端选择的话,可以直接传过来一个类名(如果怕暴露类名,可以使用名值对的形式,例如key=1,value="类名");但可能需要一个配置文件配置.不知道仁兄还其他的办法吗?  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 18:21 zeroblue

可否这样:
将Movie类声明为一个接口(感觉Price也声明为接口比较好),每增加一个Price实现类,就增加一个Movie对应的实现类。这要原来的代码不需要动,符合开闭原则。Movie接口定义子类必须实现的getCharge()方法。子类实现该方法时会调用对应的Price实现类来计算积分。
consume类代码可写成这样:
Movie regularMovie = new RegularMovie();
Movie newReleaseMovie = new NewReleaseMovie();
Movie childrenMovie = new ChildrenMovie();
(不知可行否)
  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-15 22:44 黑蝙蝠

问个问题啊 假如我现在分了很多类
1 喜剧片 2 爱情片 3 动作片 4 恐怖片 5 科幻片
6 古装片 7 动画片 8 战争片 9 连续剧 10 其它
这么多类别 也按照这种模式来做?
那岂不是有很多class文件
如果新增类型的时候又怎么办?  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-16 09:18 曲静波

@zeroblue
Movie regularMovie = new RegularMovie();
Movie newReleaseMovie = new NewReleaseMovie();
Movie childrenMovie = new ChildrenMovie();
至于将Movie声明一个接口我觉得没有必要.如果新增的话,就要新增两个类一个Price实现一个Movie实现类.也没必要添加,因为Movie做为一个电影对象,Movie里的getPrice()就能取到Price实现类的价格.而现在不加Movie接口,就已经可以遵循开闭原则.至于要把Price抽象搞成接口,其实是这样的,我想用抽象的原因是可以将抽象方法getCharge()改为方法(不抽象),这样就会有默认的getCharge()实现.其实要是用接口也可以,但还要加入一个adapter.  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-16 09:19 曲静波

@黑蝙蝠
新增类型只要加入新的类就行呀,不用修改以前的.若有配置文件,新增个配置就OK啦.  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-16 09:50 haojz

这个例子说的不错,
想想上一个朋友说用配置文件, 的确更简单,

把影片的种类, 价格和策略用配置文件来描述,这样维护和更新就更方便,
不用添加新类;  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-16 16:27 bean

这个确实是state模式吗?
我怎么感觉像是策略模式呢?
没有感觉和状态机一样,里面的状态进行改变了阿?  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-16 19:36 曲静波

@bean
老兄,这个我现在也不敢确认,我回头查下资料,确定下来具体模式.  回复  更多评论   

# re: 使用state pattern替代if else 2006-10-17 11:41 曲静波

十分对不起各位,这个实例确实应用的应属Decorator pattern,标题我就不改了。以示警戒,感谢bean老兄。  回复  更多评论   

# re: 使用decorator pattern替代if else 2006-10-18 15:25 123bingbing

增开7群,号码 30440732
8群 30756649
9群 30178567
10群 28694497

我们的qq群:15096318 学习程序的都可以来
  回复  更多评论   

# re: 使用state pattern替代if else[未登录] 2007-03-16 13:35 阿蜜果

写得不错,受益了  回复  更多评论   

# re: 使用state pattern替代if else[未登录] 2007-10-29 09:25 timothy

不错,值得学习。如果业务被确定为有大量的扩展,可以考虑使用state pattern将系统进行重构,但我比较倾向于不要一开始就大量使用模式  回复  更多评论   

# re: 使用state pattern替代if else 2008-05-06 16:27 fanlei

支持小曲老师。  回复  更多评论   

# re: 使用state pattern替代if else 2013-05-25 10:53 jehovah0121

请教为什么这不是策略模式而是状态模式?@曲静波
  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

常用链接

留言簿(3)

随笔分类(9)

随笔档案(8)

文章分类

友情链接

搜索

最新评论

阅读排行榜

评论排行榜