大多数开发人员现在还在使用if else的过程结构,曾看过jdon的banq大哥写的一篇文章,利用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 。先来个类图。

 

使用状态模式(state pattern)替代if else_System

 

首先我们建立一个 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价格基类


最新发布片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: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;
    }

}



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( " 没有该类影片 " );
        }
    }
 
}