1. 程式人生 > >使用狀態模式(state pattern)替代if else

使用狀態模式(state pattern)替代if else

http://www.blogjava.net/xzclog/archive/2006/10/16/75399.html

大多數開發人員現在還在使用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 。先來個類圖。

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
 
*/

publicabstractclass Price {

    
/**
     * 獲取租賃影片價格需實現該此方法
     * 
     * 
@param days
     *            租賃天數
     * 
@return 返回影片價格
     
*/

    
publicabstractdouble getCharge(int days);

    
/**
     * 獲取租賃影片積分需實現此方法
     * 
     * 
@param days
     *            租賃天數
     * 
@return 返回影片積分
     
*/

    
publicabstractdouble 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
 
*/

publicclass ChildrenPrice extends Price {

    
/**
     * 兒童片返回租賃積分,兒童片積分規則為: 根據
     
*/

    
publicdouble getIntegral(int days) {
        
// 返回租賃影片積分
return days *1.5;
    }


    
/**
     * 兒童片返回租賃價格
     
*/

    
publicdouble 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
 
*/

publicclass RegularlPrice extends Price {
    
/**
     * 普通片返回租賃積分,普通片積分規則
     
*/

    
publicdouble getIntegral(int days) {
        
// 返回租賃影片積分
return days *2;
    }


    
/**
     * 普通片返回租賃價格
     
*/

    
publicdouble 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:最新發布片租賃積分、價格實現