大多數(shù)開(kāi)發(fā)人員現(xiàn)在還在使用if else的過(guò)程結(jié)構(gòu),曾看過(guò)jdon的banq大哥寫(xiě)的一篇文章,利用command,aop模式替代if else過(guò)程結(jié)構(gòu)。當(dāng)時(shí)還不太明白,這幾天看了《重構(gòu)》第一章的影片租賃案例,感觸頗深。下面我來(lái)談一談為什么要用state pattern替代if else,替代if else有什么好處,以及給出詳細(xì)代碼怎么替代if else。本文參考jdon的“你還在使用if else嗎?”及《重構(gòu)》第一章。
?
首先我們模仿影片租賃過(guò)程,顧客租憑影片,影片分為兒童片、普通片、新片。根據(jù)影片類(lèi)型及租憑天數(shù)價(jià)格各不相同(優(yōu)惠程度不同),用戶(hù)累計(jì)積分不同。
?
OK
,現(xiàn)在我們使用
if else
表示。
package
?com.qujingbo.movie;


/**?*/
/**
?*?<p/>?Title:影片基類(lèi)
?*?</p>
?*?<p/>?Description:
?*?</p>
?*?<p/>?Date:2006-10-14?15:47:55
?*?</p>
?*?
?*?
@author
?EOMS?曲靜波
?*?
@version
?1.0
?
*/
public
?
class
?Movie?
{

?????
//
?普通片標(biāo)識(shí)
?????
public
?
static
?
int
?REGULAR?
=
?
1
;
????
?????
//
?新片標(biāo)識(shí)
?????
public
?
static
?
int
?NEW_RELEASE?
=
?
2
;
????
?????
//
?兒童片標(biāo)識(shí)
?????
public
?
static
?
int
?CHILDREN?
=
?
3
;
????

?????
/**?*/
/**
?????*?獲取租賃影片總價(jià)
?????*
?????*?
@param
?movieCode
?????*?影片類(lèi)型
?????*?
@param
?days
?????*?租憑天數(shù)
?????*?
@return
?租賃影片總價(jià)
?????*?
@throws
?MovieException
?????*?沒(méi)有影片類(lèi)型拋出異常
?????
*/
?????
public
?
double
?getCharge(
int
?movieCode,?
int
?days)?
throws
?MovieException?
{
?????
double
?result?
=
?
0
;
?????
//
?普通片
?????
if
?(movieCode?
==
?Movie.REGULAR)
?????
//
?單價(jià)為2
?????
{
?????result?
=
?
2
;
?????
//
?如果租賃天數(shù)大于2則,則優(yōu)惠
?????
if
?(days?
>
?
2
)?
{
?????result?
+=
?(days?
-
?
2
)?
*
?
1.5
;
?????}
?????
//
?返回總價(jià)
?????
return
?result;
?????}
?????
//
?最新發(fā)布片
?????
else
?
if
?(movieCode?
==
?Movie.NEW_RELEASE)?
{
?????
//
?新片沒(méi)有優(yōu)惠,單價(jià)為3
?????
return
?days?
*
?
3
;
?????}
?????
//
?兒童片
?????
else
?
if
?(movieCode?
==
?Movie.CHILDREN)?
{
?????
//
?影片單價(jià)
?????result?
=
?
1.5
;
?????
//
?如果租賃時(shí)間大于3天則做價(jià)格優(yōu)惠
?????
if
?(days?
>
?
3
)?
{
?????result?
+=
?(days?
-
?
3
)?
*
?
1.5
;
?????}
?????
//
?返回租賃影片總價(jià)
?????
return
?result;
?????}
?
else
?????
throw
?
new
?MovieException(
"
影片不存在
"
);
?????}
????

?????
/**?*/
/**
?????*?獲取租賃影片積分
?????*
?????*?
@param
?movieCode
?????*?影片類(lèi)型
?????*?
@param
?days
?????*?租憑天數(shù)
?????*?
@return
?租賃影片積分
?????*?
@throws
?MovieException
?????*?沒(méi)有影片類(lèi)型拋出異常
?????
*/
?????
public
?
double
?getIntegral(
int
?movieCode,?
int
?days)?
throws
?MovieException

?????
{
?????
//
?普通片
?????
if
?(movieCode?
==
?Movie.REGULAR)
?????
return
?days?
*
?
2
;
?????
//
?最新發(fā)布片
?????
else
?
if
?(movieCode?
==
?Movie.NEW_RELEASE)
?????
return
?days?
*
?
3
;
?????
//
?兒童片
?????
else
?
if
?(movieCode?
==
?Movie.CHILDREN)
?????
return
?days?
*
?
1.5
;
????????????
else
????????????????
throw
?
new
?MovieException(
"
影片不存在
"
);
????
????????}
}
OK
,我們看一下,現(xiàn)在的
Movie
完全符合租賃需求,通過(guò)
getIntegral(int movieCode,int days)
和
getCharge(int movieCode,int days)
來(lái)獲得租賃積分及租賃價(jià)格。從開(kāi)閉原則角度來(lái)看,如果要添加新的影片類(lèi)型,我們必須修改
getIntegral(int movieCode,int days)
和
getCharge(int movieCode,int days)
這兩個(gè)方法。而若要改變租賃價(jià)格、積分的優(yōu)惠規(guī)則時(shí),仍需要修改
getIntegral(int movieCode,int days)
和
getCharge(int movieCode,int days)
方法。現(xiàn)在看來(lái),只有三種影片類(lèi)型,維護(hù)還較方便。而當(dāng)影片類(lèi)型較多時(shí),例如
10
種,
100
種影片類(lèi)型,這樣就是不可以想像的維護(hù)。
?
現(xiàn)在我們來(lái)看一下,使用
state pattern
來(lái)代替
if else
。先來(lái)個(gè)類(lèi)圖。
?
?
首先我們建立一個(gè)
abstract class Price
做為影片類(lèi)型的基類(lèi),基類(lèi)中含有兩個(gè)
abstract
方法,獲取總價(jià)格
getCharge(int days),
獲取總積分
getIntegral(int days)
方法
,
繼承
abstract classPrice
的三個(gè)影片類(lèi)型兒童片
class ChilerenPrice,
普通片
class RegularPrice,
最新片
class NewReleasePrice
。分別實(shí)現(xiàn)
getCharge(int days),getIntegral(int days)
方法,實(shí)現(xiàn)方法寫(xiě)入計(jì)算價(jià)格的優(yōu)惠方案及積分的方案。當(dāng)需要修改方案時(shí),我們只需在某個(gè)影片類(lèi)的方法中對(duì)應(yīng)修改就可以。若新增一個(gè)影片分類(lèi)時(shí),我們只需新增一個(gè)實(shí)現(xiàn)類(lèi)實(shí)現(xiàn)
abstract class Price
類(lèi)就
OK
。
?
class Movie
代表影片,其關(guān)聯(lián)一個(gè)
Price
類(lèi),而
setPrice(String movieClass)
方法類(lèi)似于一個(gè)工廠(chǎng)類(lèi),傳入
movieClass
為包名類(lèi)名,用
java
反射機(jī)制實(shí)例化一個(gè)具體傳入
movieClass
的影片類(lèi)型實(shí)現(xiàn)類(lèi),這樣我們通過(guò)這幾行代碼就可以獲得該影片類(lèi)型的價(jià)格和積分。
Movie?regularMovie?
=
?
new
?Movie();
regularMovie.setPrice(Movie.REGULAR);
System.out.println(
"
普通影片租賃10天的價(jià)格
"
+
?regularMovie.getPrice().getCharge(
10
));
System.out.println(
"
普通影片租賃10天的積分
"
+
?regularMovie.getPrice().getIntegral(
10
));
下面我們給出詳細(xì)代碼
abstract class Price價(jià)格基類(lèi)
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?
{


????/**?*//**
?????*?獲取租賃影片價(jià)格需實(shí)現(xiàn)該此方法
?????*?
?????*?@param?days
?????*????????????租賃天數(shù)
?????*?@return?返回影片價(jià)格
?????*/
????public?abstract?double?getCharge(int?days);


????/**?*//**
?????*?獲取租賃影片積分需實(shí)現(xiàn)此方法
?????*?
?????*?@param?days
?????*????????????租賃天數(shù)
?????*?@return?返回影片積分
?????*/
????public?abstract?double?getIntegral(int?days);

}

兒童片ChildrenPrice類(lèi),實(shí)現(xiàn)abstract class Price ,實(shí)現(xiàn)兒童片租賃總價(jià)getCharge(int days)及兒童片租賃積分getIntegral(int days)。
package?com.qujingbo.movie;


/**?*//**
?*?<p/>?Title:兒童片租賃積分、價(jià)格實(shí)現(xiàn)
?*?</p>
?*?<p/>?Description:
?*?</p>
?*?<p/>?Date:2006-10-14?15:49:04
?*?</p>
?*?
?*?@author?EOMS?曲靜波
?*?@version?1.0
?*/

public?class?ChildrenPrice?extends?Price?
{


????/**?*//**
?????*?兒童片返回租賃積分,兒童片積分規(guī)則為:?根據(jù)
?????*/

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


????/**?*//**
?????*?兒童片返回租賃價(jià)格
?????*/

????public?double?getCharge(int?days)?
{
????????//?影片單價(jià)
????????double?result?=?1.5;
????????//?如果租賃時(shí)間大于3天則做價(jià)格優(yōu)惠

????????if?(days?>?3)?
{
????????????result?+=?(days?-?3)?*?1.5;
????????}
????????//?返回租賃影片總價(jià)
????????return?result;
????}

}

普通片RegularlPrice類(lèi),實(shí)現(xiàn)abstract class Price ,實(shí)現(xiàn)普通片租賃總價(jià)getCharge(int days)及普通片租賃積分getIntegral(int days)。
package?com.qujingbo.movie;


/**?*//**
?*?<p/>?Title:普通片租賃積分、價(jià)格實(shí)現(xiàn)
?*?</p>
?*?<p/>?Description:
?*?</p>
?*?<p/>?Date:2006-10-14?15:50:10
?*?</p>
?*?
?*?@author?EOMS?曲靜波
?*?@version?1.0
?*/

public?class?RegularlPrice?extends?Price?
{

????/**?*//**
?????*?普通片返回租賃積分,普通片積分規(guī)則
?????*/

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


????/**?*//**
?????*?普通片返回租賃價(jià)格
?????*/

????public?double?getCharge(int?days)?
{
????????//?單價(jià)為2
????????double?result?=?2;
????????//?如果租賃天數(shù)大于2則,則優(yōu)惠

????????if?(days?>?2)?
{
????????????result?+=?(days?-?2)?*?1.5;
????????}
????????//?返回總價(jià)
????????return?result;
????}

}

最新發(fā)布片NewReleasePrice類(lèi),實(shí)現(xiàn)abstract class Price ,實(shí)現(xiàn)最新發(fā)布片租賃總價(jià)getCharge(int days)及最新發(fā)布片租賃積分getIntegral(int days)。
package?com.qujingbo.movie;


/**?*//**
?*?<p/>?Title:最新發(fā)布片租賃積分、價(jià)格實(shí)現(xiàn)
?*?</p>
?*?<p/>?Description:
?*?</p>
?*?<p/>?Date:2006-10-14?15:48:51
?*?</p>
?*?
?*?@author?EOMS?曲靜波
?*?@version?1.0
?*/

public?class?NewReleasePrice?extends?Price?
{

????/**?*//**
?????*?最新發(fā)布片返回租賃積分,最新發(fā)布片積分規(guī)則
?????*/

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


????/**?*//**
?????*?最新發(fā)布片返回租賃價(jià)格
?????*/

????public?double?getCharge(int?days)?
{
????????//?新片沒(méi)有優(yōu)惠,單價(jià)為3
????????return?days?*?3;
????}

}

電影Movie類(lèi),setPrice(String movieClass)(工廠(chǎng))方法,通過(guò)java反射機(jī)制實(shí)現(xiàn)movieClass(包名,類(lèi)名)類(lèi)。若沒(méi)有movieClass這個(gè)類(lèi),則拋出MovieException異常。
package?com.qujingbo.movie;


/**?*//**
?*?<p/>?Title:影片類(lèi)
?*?</p>
?*?<p/>?Description:
?*?</p>
?*?<p/>?Date:2006-10-14?15:47:55
?*?</p>
?*?
?*?@author?EOMS?曲靜波
?*?@version?1.0
?*/

public?class?Movie?
{
????//?普通片標(biāo)識(shí)
????public?static?String?REGULAR?=?"com.qujingbo.movie.RegularlPrice";

????//?新片標(biāo)識(shí)
????public?static?String?NEW_RELEASE?=?"com.qujingbo.movie.NewReleasePrice";

????//?兒童片標(biāo)識(shí)
????public?static?String?CHILDREN?=?"com.qujingbo.movie.ChildrenPrice";

????private?Price?price;


????public?Price?getPrice()?
{
????????return?price;
????}


????/**?*//**
?????*?確定返回具體某個(gè)影片類(lèi)型的實(shí)現(xiàn)類(lèi),有點(diǎn)像工廠(chǎng)
?????*?
?????*?@param?movieCode
?????*????????????影片類(lèi)型
?????*?@throws?MovieException
?????*?????????????若無(wú)影片類(lèi)型則拋異常。
?????*/

????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);
????}
}

下面模訪(fǎng)一個(gè)顧客租賃影片。
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?
{

????/**?*//**
?????*?消費(fèi)(測(cè)試程序)
?????*?
?????*?@throws?MovieException
?????*?????????????若沒(méi)有影片,拋出異常
?????*/

????public?void?consume()?throws?MovieException?
{
????????//?普通電影
????????Movie?regularMovie?=?new?Movie();
????????regularMovie.setPrice(Movie.REGULAR);
????????//?最新發(fā)布電影
????????Movie?newReleaseMovie?=?new?Movie();
????????newReleaseMovie.setPrice(Movie.NEW_RELEASE);
????????//?兒童電影
????????Movie?childrenMovie?=?new?Movie();
????????childrenMovie.setPrice(Movie.CHILDREN);

????????System.out.println("普通影片租賃10天的價(jià)格"
????????????????+?regularMovie.getPrice().getCharge(10));
????????System.out.println("最新影片租賃10天的價(jià)格"
????????????????+?newReleaseMovie.getPrice().getCharge(10));
????????System.out.println("兒童影片租賃10天的價(jià)格"
????????????????+?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));
????????
????????
????}
}

寫(xiě)一
junit
測(cè)試類(lèi)運(yùn)行
class Customer
的
consume()
方法。
package
?com.qujingbo.movie;

import
?junit.framework.TestCase;


/**?*/
/**
?*?<p/>?Title:junit測(cè)試類(lèi)
?*?</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(
"
沒(méi)有該類(lèi)影片
"
);
????????}
????}
}
OK
。結(jié)果為:
普通影片租賃
10
天的價(jià)格
14.0
最新影片租賃
10
天的價(jià)格
30.0
兒童影片租賃
10
天的價(jià)格
12.0
普通影片租賃
10
天的積分
20.0
最新影片租賃
10
天的積分
30.0
兒童影片租賃
10
天的積分
15.0
?
最后我要說(shuō),我們用
OO
表示的租賃過(guò)程并不完整,因?yàn)轭櫩筒灰欢ㄖ蛔赓U一部影片,而要租賃多部影片,這樣我們?nèi)鄙僖粋€(gè)
Rental
(租賃類(lèi))。而只是為說(shuō)明
state pattern
替代
if else
,所以我們沒(méi)有添加
Rental
(租賃類(lèi)),若需要參考,請(qǐng)查閱《重構(gòu)》第一章。 點(diǎn)擊下載源碼.
?
這是我第一次寫(xiě)技術(shù)文章,如果有適當(dāng)?shù)牡胤剑?qǐng)各位朋友提出各自見(jiàn)解。
?
email:qujingbo@gmail.com