為了能在Java應(yīng)用程序中正確的使用狀態(tài)模式和策略模式,開發(fā)人員需要清楚地知道這兩種模式之間的區(qū)別。盡管狀態(tài)模式和策略模式的結(jié)構(gòu)非常相似,它們又同樣遵循開閉原則,都代表著SOLID設(shè)計(jì)原則的'O',但它們的意圖是完全不同的。Java中的策略模式是對一組相關(guān)的算法進(jìn)行封裝,給調(diào)用方提供了運(yùn)行時的靈活性。調(diào)用方可以在運(yùn)行時選擇不同的算法,而不用修改使用策略的那個Context類。使用策略模式的經(jīng)典例子包括實(shí)現(xiàn)加密算法,壓縮算法,以及排序算法。另一方面,狀態(tài)模式使用一個對象可以在不同的狀態(tài)下表現(xiàn)出不同的行為。真實(shí)世界里的對象也是有狀態(tài)的,并且它們會隨著狀態(tài)的不同而有不同的表現(xiàn),比方說自動售貨機(jī),它只會在hasCoin狀態(tài)下才能出售物品,如果你不塞硬幣進(jìn)去它是不會售貨的。現(xiàn)在你可以很清楚地看到策略模式和狀態(tài)模式的區(qū)別了,它們的目的是不一樣的。狀態(tài)模式可以幫助對象來管理它的狀態(tài),而策略模式使得客戶端可以選擇不同的行為。還有一個不太容易看到的區(qū)別是,誰去驅(qū)動行為的改變。在策略模式中,是客戶端驅(qū)動的,它給上下文信息提供了不同的策略,而在狀態(tài)模式中,狀態(tài)的遷移是由Context或者State對象自己來管理的。同樣的,如果你在State對象里面進(jìn)行狀態(tài)的修改,它必須持有Context的引用,也就是說對自動售貨機(jī)而言,它可以調(diào)用setState方法來修改當(dāng)前Context的狀態(tài)。另一方面,策略對象不會持有Context的引用 ,它的客戶端將選中的策略傳遞給Context。策略模式和狀態(tài)模式是最容易碰見的關(guān)于Java設(shè)計(jì)模式的面試題,在這篇關(guān)于Java設(shè)計(jì)模式的文章里,我們將會對這點(diǎn)進(jìn)行詳細(xì)的介紹。我們會探索這兩種模式的相同點(diǎn)與不同點(diǎn),這有助于提高你對這兩種模式的理解。
狀態(tài)模式和策略模式的相似點(diǎn):
如果你看下策略模式和狀態(tài)模式的UML圖,它們看起來非常相似。在狀態(tài)模式中,使用State對象來改變行為的的對象叫Context對象,類似的在策略模式中,使用Strategy對象來改變行為的對象也是Context對象。記住,客戶端是和Context對象交互的。在狀態(tài)模式中,Context代理了狀態(tài)對象的方法調(diào)用,Context中的當(dāng)前對象就是具體的狀態(tài)對象,而在策略模式中,Context操作的也是策略對象,這個對象要么作為參數(shù)傳入進(jìn)來,要么是在創(chuàng)建Context對象的時候就已經(jīng)提供了。
我們再來看一下這兩種核心的Java設(shè)計(jì)模式的一些相似點(diǎn):
狀態(tài)模式和策略模式都很容易新增新的狀態(tài)或者策略,而不會影響到使用它們的Context對象
兩種模式都遵循開閉的設(shè)計(jì)原則,也就是說你的設(shè)計(jì)對擴(kuò)展開放而對修改關(guān)閉。在這兩個模式里,Context對修改是封閉的,新增狀態(tài)或者策略,你不需要修改其它狀態(tài)的Context對象,或者只需要很小的改動
正如狀態(tài)模式中Context對象會有一個初始狀態(tài)一樣,策略模式中的Context通常也有一個默認(rèn)的策略。
狀態(tài)模式以不同的狀態(tài)對象的方式來封裝不同的行為,而策略模式以不同的策略對象來封裝不同的行為。
這兩種模式都依賴具體的子類來實(shí)現(xiàn)具體的行為。每一個具體的策略都擴(kuò)展自一個抽象的策略類,每個狀態(tài)也都是用來表示狀態(tài)的接口或者抽象類的子類。
狀態(tài)模式實(shí)例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
public class WindowState { private String stateValue; public WindowState(String stateValue) { this .stateValue = stateValue; } public String getStateValue() { return stateValue; } public void setStateValue(String stateValue) { this .stateValue = stateValue; } public void handle() { /* * 根據(jù)不同狀態(tài)做不同操作, 再切換狀態(tài) */ if ( "窗口" .equals(stateValue)) { switchWindow(); this .stateValue = "全屏" ; } else if ( "全屏" .equals(stateValue)) { switchFullscreen(); this .stateValue = "窗口" ; } } private void switchWindow() { System.out.println( "切換為窗口狀態(tài)" ); } private void switchFullscreen() { System.out.println( "切換為全屏狀態(tài)" ); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/** * 狀態(tài)的使用 */ public class WindowContext { private WindowState state; public WindowContext(WindowState state) { this .state = state; } public WindowState getState() { return state; } public void setState(WindowState state) { this .state = state; } public void switchState() { this .state.handle(); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/* * 狀態(tài)(State)模式 行為型模式 * 既改變對象的狀態(tài),又改變對象的行為 * 根據(jù)狀態(tài),改變行為 */ public class Test { public static void main(String[] args) { /* * 本例的 狀態(tài)值只有兩個,由狀態(tài)類自身控制 * 也可以把狀態(tài)值的控制,交由客戶端來設(shè)置 */ WindowContext context = new WindowContext( new WindowState( "窗口" )); context.switchState(); context.switchState(); context.switchState(); context.switchState(); } } |
打印
1
2
3
4
|
切換為窗口狀態(tài) 切換為全屏狀態(tài) 切換為窗口狀態(tài) 切換為全屏狀態(tài) |
策略模式實(shí)例
1
2
3
4
5
6
7
|
/** * 商品促銷 * 本類為:收取現(xiàn)金的類 */ public interface ICashSuper { double acceptCash( double money); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** * 正常收取現(xiàn)金 * @author stone * */ public class CashNormal implements ICashSuper { @Override public double acceptCash( double money) { return money; } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/** * 打折收取現(xiàn)金 * @author stone * */ public class CashRebate implements ICashSuper { private double rebate; //折扣 public CashRebate ( double rebate) { this .rebate = rebate; } @Override public double acceptCash( double money) { return new BigDecimal(money * rebate / 10 ).setScale( 2 , BigDecimal.ROUND_HALF_UP).doubleValue(); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/** * 讓利返現(xiàn) 收取現(xiàn)金 * @author stone * */ public class CashReturn implements ICashSuper { private double moneyCondition; //返現(xiàn)底限金額 private double returnMoney; //返還金額 public CashReturn( double moneyCondition, double returnMoney) { this .moneyCondition = moneyCondition; this .returnMoney = returnMoney; } @Override public double acceptCash( double money) { //多重返利 if (money >= moneyCondition) { return money - Math.floor(money / moneyCondition) * returnMoney; } else { return money; } } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/** * 根據(jù)傳遞的的策略類,執(zhí)行相應(yīng)的行為 */ public class CashContext { private ICashSuper casher; public CashContext() { } public CashContext(ICashSuper casher) { this .casher = casher; } public void setCasher(ICashSuper casher) { this .casher = casher; } //根據(jù)具體的策略對象,調(diào)用它的算法行為 public double acceptCash( double money) { return this .casher.acceptCash(money); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Test { public static void main(String[] args) { double money = 998 ; //原價(jià) CashContext cashContext = new CashContext( new CashNormal()); System.out.println( "原價(jià):" + cashContext.acceptCash(money)); //通常 策略 cashContext.setCasher( new CashRebate( 8.5 )); System.out.println( "打85折:" + cashContext.acceptCash(money)); //折扣 策略 85折 cashContext.setCasher( new CashReturn( 300 , 50 )); System.out.println( "滿300 返50:" + cashContext.acceptCash(money)); //返現(xiàn) 策略 滿300 返50 } } |
打印
1
2
3
|
原價(jià):998.0 打85折:848.3 滿300 返50:848.0 |
策略模式和狀態(tài)模式的區(qū)別
我們已經(jīng)了解到這兩個模式在結(jié)構(gòu)上非常相似,但它們?nèi)杂胁煌牡胤健O旅鎭砜聪滤鼈冎g一些關(guān)鍵的不同點(diǎn)。
- 策略模式封裝了一系列的相關(guān)的算法,使用客戶端可以在運(yùn)行時通過組合和委托來使用不同的行為,而狀態(tài)模式使得對象可以在不同的狀態(tài)下展現(xiàn)出不同的行為。
- 這兩個模式的另一個不同之處在于狀態(tài)模式封裝的是對象的狀態(tài),而策略模式封裝的是一個算法或者策略。由于狀態(tài)是和對象耦合在一起的,它無法重用,而通過策略或者算法獨(dú)立于它的上下文,使得它們可以重復(fù)使用。
- 狀態(tài)模式中,狀態(tài)本身會包含Context的引用,從而實(shí)現(xiàn)狀態(tài)遷移 ,但策略模式則沒有Context的引用
- 具體的策略可以作為一個參數(shù)傳遞給使用它們的對象,比如說Collections.sort()接受一個Comparator,這是一個策略。另狀態(tài)本身 是 Context對象的一部分,隨著時間的遷移,Context對象會從一個狀態(tài)遷移遷移到另一個狀態(tài)下。
- 盡管兩種模式都遵循了開閉原則,策略模式還遵循了單一職責(zé)原則,因?yàn)槊總€策略都 封裝的是獨(dú)立 的算法,不同的策略獨(dú)立于其它策略。改變一個策略并不會影響到另一個策略的實(shí)現(xiàn)。
- 從理論上說,策略模式和狀態(tài)模式還有一個不同,前者定義的是一個對象“如何”去做一件事情,比如說如何對數(shù)據(jù)進(jìn)行排序,而另一方面,狀態(tài)模式定義的是“什么”以及“何時“,比如說一個對象能做什么,某個時間點(diǎn)它處于哪個狀態(tài)。
- 狀態(tài)的遷移順序在狀態(tài)模式中是定義好的,而策略模式則沒有這樣的要求。客戶端可以隨便選擇使用哪個策略。
- 常見的策略模式的例子都是封裝算法,比如說排序算法,加密算法,或者壓縮算法。如果你發(fā)現(xiàn)代碼中需要使用到不同的算法,那么你可以考慮使用策略模式。而如果你需要管理狀態(tài)進(jìn)行狀態(tài)間的遷移,而不希望嵌套許多條件語句,那么狀態(tài)模式就是你的首選,因?yàn)樗浅:唵?
- 最后也是最重要的一個區(qū)別在于,策略模式是由客戶端進(jìn)行處理的,而狀態(tài)的改變Context或者State對象都可以進(jìn)行。
這就是關(guān)于Java中策略模式和狀態(tài)模式的所有區(qū)別。正如我所說的,它們在UML圖中看起來非常類似,兩者都遵循了開閉原則,并且封裝了行為。策略模式是用來封裝算法或者策略的,它會在運(yùn)行時作為參數(shù)或者組合對象來提供給Context對象,而狀態(tài)模式則是用來管理狀態(tài)遷移 的。