模式介紹
狀態(tài)模式的結(jié)構(gòu)和策略模式幾乎一模一樣。
但是它們兩者的目的、本質(zhì)卻完全不同。
策略模式的行為是彼此獨(dú)立,相互替換的?;叵胫芭e的價(jià)格計(jì)算器,我們出行時(shí)想使用地鐵,則使用地鐵的價(jià)格計(jì)算器,如果使用出租車,則使用出租車的價(jià)格計(jì)算器......
而狀態(tài)模式的行為則是平行的,不可替換的。狀態(tài)模式更像是被封裝在對象內(nèi)部的一個(gè)東西,當(dāng)對象的狀態(tài)發(fā)生變化時(shí),其行為也要發(fā)生變化。
使用場景
- 一個(gè)對象的行為需要根據(jù)狀態(tài)發(fā)生變化,尤其是在運(yùn)行時(shí)狀態(tài)發(fā)生變化,行為需要跟著一起變化時(shí);
- 代碼中有大量判斷語句(if、switch),且這些語句依賴該對象的狀態(tài)。
模式角色
- State:狀態(tài)接口,定義所有行為的抽象。
- ConcreteStateA、ConcreteStateB:某個(gè)狀態(tài)的具體行為實(shí)現(xiàn),一個(gè)狀態(tài)對應(yīng)一個(gè)具體行為實(shí)現(xiàn)。
模式示例
相信大家在開發(fā)中,只要是動態(tài)App(和服務(wù)器有交互),都會有登錄的需求。
針對這個(gè)需求,我們可以得出兩個(gè)狀態(tài):登錄狀態(tài)、未登錄狀態(tài)。
接著我們可以想象一些行為:
- 登錄
- 退出登錄
- 查看賬戶金幣
- 賺取金幣
- 金幣兌換道具
針對上述行為,在登錄狀態(tài)和未登錄狀態(tài)下,所做的事情是完全不同的:
- 登錄狀態(tài):
- 登錄:提示用戶已經(jīng)登錄過了,請勿重復(fù)登錄
- 退出登錄:清除用戶緩存并提示用戶退出登錄成功
- 查看賬戶金幣:從服務(wù)器查詢用戶余額
- 賺取金幣:跳轉(zhuǎn)到賺金幣頁面
- 金幣兌換道具:跳轉(zhuǎn)到兌換道具頁
- 未登錄狀態(tài):
- 登錄:判斷用戶輸入的賬號密碼,正確提示用戶登錄成功。
- 退出登錄:提示用戶已經(jīng)退出過了。
- 查看賬戶金幣:提示用戶請先登錄
- 賺取金幣:跳轉(zhuǎn)到賺金幣頁(這里也可以提示用戶登錄,具體看產(chǎn)品定的登錄時(shí)機(jī))
- 金幣兌換道具:提示用戶請先登錄
想必看完這個(gè)示例,大家已經(jīng)對狀態(tài)模式有了一些自己的見解。
這種情況下,非常適合使用狀態(tài)模式。
如果項(xiàng)目中,還是通過判斷語句來進(jìn)行狀態(tài)的區(qū)分,就說明這個(gè)項(xiàng)目沒有很好地應(yīng)用狀態(tài)模式。
下面我們就來將上述案例轉(zhuǎn)化成代碼。
首先是狀態(tài)行為的抽象,該抽象包含了所有的行為:
public interface UserState {
//登錄
void login();
//登出
void logout();
//查詢余額
void seeMoney();
//賺金幣
void earnMoney();
//兌換道具
void exchange();
}
針對我們上面描述的示例,有以上5種行為,我們將其進(jìn)行了抽象。
接著來思考行為的具體實(shí)現(xiàn):登錄狀態(tài)的具體實(shí)現(xiàn)、未登錄狀態(tài)的具體實(shí)現(xiàn)。
具體做起來也很簡單,就是創(chuàng)建兩個(gè)類去實(shí)現(xiàn)我們的行為抽象,在各自的實(shí)現(xiàn)下,去實(shí)現(xiàn)該狀態(tài)下,應(yīng)該做的事情。
首先來看登錄狀態(tài):
public class LoginStateImpl implements UserState {
@Override
public void login() {
Toast.makeText(App.context, "您已經(jīng)登錄了,無需登錄!", Toast.LENGTH_SHORT).show();
}
@Override
public void logout() {
Toast.makeText(App.context, "退出登錄成功!", Toast.LENGTH_SHORT).show();
}
@Override
public void seeMoney() {
Toast.makeText(App.context, "您目前有100金幣", Toast.LENGTH_SHORT).show();
}
@Override
public void earnMoney() {
Toast.makeText(App.context, "跳轉(zhuǎn)到-賺金幣", Toast.LENGTH_SHORT).show();
}
@Override
public void exchange() {
Toast.makeText(App.context, "跳轉(zhuǎn)到-兌換道具", Toast.LENGTH_SHORT).show();
}
}
根據(jù)示例需求,我實(shí)現(xiàn)了登錄狀態(tài)下,這5種行為的具體實(shí)現(xiàn)。
未登錄狀態(tài)也是同理,這里就不列舉代碼了,可以直接查看GitHub。
接下來我們來看一下測試代碼:
//默認(rèn)是未登錄狀態(tài)
private UserState userState = new LogutStateImpl();
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_login://登錄行為
userState.login();
//核心:更換用戶狀態(tài)
userState = new LoginStateImpl();
break;
case R.id.bt_logout://退出登錄行為
userState.logout();
userState = new LogutStateImpl();
break;
case R.id.bt_see_money://查看余額行為
userState.seeMoney();
break;
case R.id.bt_earn_money://賺取金幣行為
userState.earnMoney();
break;
case R.id.bt_exchange://兌換行為
userState.exchange();
break;
}
}
這里也是利用多態(tài)的特性,根據(jù)狀態(tài)的變化,注入不同的實(shí)現(xiàn)。
至此,我們已經(jīng)使用狀態(tài)模式成功實(shí)現(xiàn)了上述需求,并且去除了重復(fù)、雜亂的判斷語句,體現(xiàn)出了狀態(tài)模式的精髓。
總結(jié)
代碼已經(jīng)上傳至GitHub,可以下載查閱。
其實(shí)看到這里,各位看官想必已經(jīng)了解了狀態(tài)模式。
狀態(tài)模式的關(guān)鍵點(diǎn)在于:不同狀態(tài)下、同一行為的不同響應(yīng)。
狀態(tài)模式是為了優(yōu)化代碼結(jié)構(gòu)而產(chǎn)生的。我們通過if-else其實(shí)可以完美判斷用戶的登錄狀態(tài),但是這種實(shí)現(xiàn)使得邏輯與具體行為耦合在一起,后期難以維護(hù)。
狀態(tài)模式就是為了消除這種丑態(tài),實(shí)現(xiàn)邏輯與行為的解耦。
當(dāng)然并不是所有的if-else都適合使用狀態(tài)模式,具體是否使用,還是由你來決定。