Java如何利用狀態(tài)模式(state pattern)替代if else
大多數(shù)開發(fā)人員現(xiàn)在還在使用if else的過程結(jié)構(gòu),曾看過jdon的banq大哥寫的一篇文章,利用command,aop模式替代if else過程結(jié)構(gòu)。當(dāng)時還不太明白,這幾天看了《重構(gòu)》第一章的影片租賃案例,感觸頗深。下面我來談一談為什么要用state pattern替代if else,替代if else有什么好處,以及給出詳細(xì)代碼怎么替代if else。本文參考jdon的“你還在使用if else嗎?”及《重構(gòu)》第一章。
首先我們模仿影片租賃過程,顧客租憑影片,影片分為兒童片、普通片、新片。根據(jù)影片類型及租憑天數(shù)價格各不相同(優(yōu)惠程度不同),用戶累計積分不同。
OK ,現(xiàn)在我們使用 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 {
// 普通片標(biāo)識
public static int REGULAR = 1 ;
// 新片標(biāo)識
public static int NEW_RELEASE = 2 ;
// 兒童片標(biāo)識
public static int CHILDREN = 3 ;
/**
* 獲取租賃影片總價
*
* @param movieCode
* 影片類型
* @param days
* 租憑天數(shù)
* @return 租賃影片總價
* @throws MovieException
* 沒有影片類型拋出異常
*/
public double getCharge( int movieCode, int days) throws MovieException {
double result = 0 ;
// 普通片
if (movieCode == Movie.REGULAR)
// 單價為2
{
result = 2 ;
// 如果租賃天數(shù)大于2則,則優(yōu)惠
if (days > 2 ) {
result += (days - 2 ) * 1.5 ;
}
// 返回總價
return result;
}
// 最新發(fā)布片
else if (movieCode == Movie.NEW_RELEASE) {
// 新片沒有優(yōu)惠,單價為3
return days * 3 ;
}
// 兒童片
else if (movieCode == Movie.CHILDREN) {
// 影片單價
result = 1.5 ;
// 如果租賃時間大于3天則做價格優(yōu)惠
if (days > 3 ) {
result += (days - 3 ) * 1.5 ;
}
// 返回租賃影片總價
return result;
} else
throw new MovieException( " 影片不存在 " );
}
/**
* 獲取租賃影片積分
*
* @param movieCode
* 影片類型
* @param days
* 租憑天數(shù)
* @return 租賃影片積分
* @throws MovieException
* 沒有影片類型拋出異常
*/
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 完全符合租賃需求,通過 getIntegral(int movieCode,int days) 和 getCharge(int movieCode,int days) 來獲得租賃積分及租賃價格。從開閉原則角度來看,如果要添加新的影片類型,我們必須修改 getIntegral(int movieCode,int days) 和 getCharge(int movieCode,int days) 這兩個方法。而若要改變租賃價格、積分的優(yōu)惠規(guī)則時,仍需要修改 getIntegral(int movieCode,int days) 和 getCharge(int movieCode,int days) 方法?,F(xiàn)在看來,只有三種影片類型,維護(hù)還較方便。而當(dāng)影片類型較多時,例如 10 種, 100 種影片類型,這樣就是不可以想像的維護(hù)。
現(xiàn)在我們來看一下,使用 state pattern 來代替 if else 。先來個類圖。

首先我們建立一個 abstract class Price 做為影片類型的基類,基類中含有兩個 abstract 方法,獲取總價格 getCharge(int days), 獲取總積分 getIntegral(int days) 方法 , 繼承 abstract classPrice 的三個影片類型兒童片 class ChilerenPrice, 普通片 class RegularPrice, 最新片 class NewReleasePrice 。分別實(shí)現(xiàn) getCharge(int days),getIntegral(int days) 方法,實(shí)現(xiàn)方法寫入計算價格的優(yōu)惠方案及積分的方案。當(dāng)需要修改方案時,我們只需在某個影片類的方法中對應(yīng)修改就可以。若新增一個影片分類時,我們只需新增一個實(shí)現(xiàn)類實(shí)現(xiàn) abstract class Price 類就 OK 。
class Movie 代表影片,其關(guān)聯(lián)一個 Price 類,而 setPrice(String movieClass) 方法類似于一個工廠類,傳入 movieClass 為包名類名,用 java 反射機(jī)制實(shí)例化一個具體傳入 movieClass 的影片類型實(shí)現(xiàn)類,這樣我們通過這幾行代碼就可以獲得該影片類型的價格和積分。
Movie regularMovie = new Movie(); regularMovie.setPrice(Movie.REGULAR); System.out.println( " 普通影片租賃10天的價格 " + regularMovie.getPrice().getCharge( 10 )); System.out.println( " 普通影片租賃10天的積分 " + regularMovie.getPrice().getIntegral( 10 ));
下面我們給出詳細(xì)代碼
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 {
/**
* 獲取租賃影片價格需實(shí)現(xiàn)該此方法
*
* @param days
* 租賃天數(shù)
* @return 返回影片價格
*/
public abstract double getCharge(int days);
/**
* 獲取租賃影片積分需實(shí)現(xiàn)此方法
*
* @param days
* 租賃天數(shù)
* @return 返回影片積分
*/
public abstract double getIntegral(int days);
}
兒童片ChildrenPrice類,實(shí)現(xiàn)abstract class Price ,實(shí)現(xiàn)兒童片租賃總價getCharge(int days)及兒童片租賃積分getIntegral(int days)。
package com.qujingbo.movie;
/**
* <p/> Title:兒童片租賃積分、價格實(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;
}
/**
* 兒童片返回租賃價格
*/
public double getCharge(int days) {
// 影片單價
double result = 1.5;
// 如果租賃時間大于3天則做價格優(yōu)惠
if (days > 3) {
result += (days - 3) * 1.5;
}
// 返回租賃影片總價
return result;
}
}
普通片RegularlPrice類,實(shí)現(xiàn)abstract class Price ,實(shí)現(xiàn)普通片租賃總價getCharge(int days)及普通片租賃積分getIntegral(int days)。
package com.qujingbo.movie;
/**
* <p/> Title:普通片租賃積分、價格實(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;
}
/**
* 普通片返回租賃價格
*/
public double getCharge(int days) {
// 單價為2
double result = 2;
// 如果租賃天數(shù)大于2則,則優(yōu)惠
if (days > 2) {
result += (days - 2) * 1.5;
}
// 返回總價
return result;
}
}
最新發(fā)布片NewReleasePrice類,實(shí)現(xiàn)abstract class Price ,實(shí)現(xiàn)最新發(fā)布片租賃總價getCharge(int days)及最新發(fā)布片租賃積分getIntegral(int days)。
package com.qujingbo.movie;
/**
* <p/> Title:最新發(fā)布片租賃積分、價格實(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ā)布片返回租賃價格
*/
public double getCharge(int days) {
// 新片沒有優(yōu)惠,單價為3
return days * 3;
}
}
電影Movie類,setPrice(String movieClass)(工廠)方法,通過java反射機(jī)制實(shí)現(xiàn)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 {
// 普通片標(biāo)識
public static String REGULAR = "com.qujingbo.movie.RegularlPrice";
// 新片標(biāo)識
public static String NEW_RELEASE = "com.qujingbo.movie.NewReleasePrice";
// 兒童片標(biāo)識
public static String CHILDREN = "com.qujingbo.movie.ChildrenPrice";
private Price price;
public Price getPrice() {
return price;
}
/**
* 確定返回具體某個影片類型的實(shí)現(xiàn)類,有點(diǎn)像工廠
*
* @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 {
/**
* 消費(fèi)(測試程序)
*
* @throws MovieException
* 若沒有影片,拋出異常
*/
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天的價格"
+ 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 測試類運(yùn)行 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 。結(jié)果為:
普通影片租賃 10 天的價格 14.0
最新影片租賃 10 天的價格 30.0
兒童影片租賃 10 天的價格 12.0
普通影片租賃 10 天的積分 20.0
最新影片租賃 10 天的積分 30.0
兒童影片租賃 10 天的積分 15.0
最后我要說,我們用 OO 表示的租賃過程并不完整,因?yàn)轭櫩筒灰欢ㄖ蛔赓U一部影片,而要租賃多部影片,這樣我們?nèi)鄙僖粋€ Rental (租賃類)。而只是為說明 state pattern 替代 if else ,所以我們沒有添加 Rental (租賃類),若需要參考,請查閱《重構(gòu)》第一章。 點(diǎn)擊下載源碼.
到此這篇關(guān)于Java如何利用狀態(tài)模式(state pattern)替代if else的文章就介紹到這了,更多相關(guān)Java用狀態(tài)模式(state pattern)替代if else內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)獲取行政區(qū)劃的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java語言實(shí)現(xiàn)獲取行政區(qū)劃的功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)游戲2023-03-03
Springboot基于Redisson實(shí)現(xiàn)Redis分布式可重入鎖源碼解析
這篇文章主要介紹了Springboot基于Redisson實(shí)現(xiàn)Redis分布式可重入鎖,本文通過案例源碼分析給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-03-03
mybatis?<foreach>標(biāo)簽動態(tài)增刪改查方式
這篇文章主要介紹了mybatis?<foreach>標(biāo)簽動態(tài)增刪改查方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
SpringBoot自定義注解實(shí)現(xiàn)Token校驗(yàn)的方法
這篇文章主要介紹了SpringBoot自定義注解實(shí)現(xiàn)Token校驗(yàn)的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
如何使用Spring MVC的消息轉(zhuǎn)換器設(shè)置日期格式
這篇文章主要介紹了如何使用Spring MVC的消息轉(zhuǎn)換器設(shè)置日期格式,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07

