1、狀態(tài)模式概念
1.1 介紹
在狀態(tài)模式(State Pattern)中,類的行為是基于它的狀態(tài)改變的。這種類型的設(shè)計(jì)模式屬于行為型模式。
在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對(duì)象和一個(gè)行為隨著狀態(tài)對(duì)象改變而改變的 context 對(duì)象。
1.2 定義
允許對(duì)象在內(nèi)部狀態(tài)發(fā)生改變時(shí)改變它的行為,對(duì)象看起來(lái)好像修改了它的類。
1.3 使用場(chǎng)景
行為隨狀態(tài)改變而改變的場(chǎng)景
例如權(quán)限設(shè)計(jì),人員的狀態(tài)不同即時(shí)執(zhí)行相同的行為結(jié)果也會(huì)不同,在這種情況下需要考慮使用狀態(tài)模式。條件、分支判斷語(yǔ)句的替代者
在程序中大量使用switch語(yǔ)句或者if判斷語(yǔ)句會(huì)導(dǎo)致程序結(jié)構(gòu)不清晰,邏輯混亂,使用狀態(tài)模式可以很好地避免這一問(wèn)題,它通過(guò)擴(kuò)展子類實(shí)現(xiàn)了條件的判斷處理。
2、狀態(tài)模式UML類圖

角色如下:
- 環(huán)境類(Context): 定義客戶感興趣的接口。維護(hù)一個(gè)ConcreteState子類的實(shí)例,這個(gè)實(shí)例定義當(dāng)前狀態(tài)。
- 抽象狀態(tài)類(State): 定義一個(gè)接口以封裝與Context的一個(gè)特定狀態(tài)相關(guān)的行為。
- 具體狀態(tài)類(ConcreteState): 每一子類實(shí)現(xiàn)一個(gè)與Context的一個(gè)狀態(tài)相關(guān)的行為。
3、狀態(tài)模式實(shí)現(xiàn)
Context:
public class Context {
//定義出所有的電梯狀態(tài)
public final static OpenningState openningState = new OpenningState();
public final static ClosingState closeingState = new ClosingState();
public final static RunningState runningState = new RunningState();
public final static StoppingState stoppingState = new StoppingState();
//定一個(gè)當(dāng)前電梯狀態(tài)
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//把當(dāng)前的環(huán)境通知到各個(gè)實(shí)現(xiàn)類中
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
State:
public abstract class LiftState{
//定義一個(gè)環(huán)境角色,也就是封裝狀態(tài)的變換引起的功能變化
protected Context context;
public void setContext(Context _context) {
this.context = _context;
}
//首先電梯門開(kāi)啟動(dòng)作
public abstract void open();
//電梯門有開(kāi)啟,那當(dāng)然也就有關(guān)閉了
public abstract void close();
//電梯要能上能下,跑起來(lái)
public abstract void run();
//電梯還要能停下來(lái),停不下來(lái)那就扯淡了
public abstract void stop();
}
ConcreteState:
public class ClosingState extends LiftState {
//電梯門關(guān)閉,這是關(guān)閉狀態(tài)要實(shí)現(xiàn)的動(dòng)作
@Override
public void close() {
System.out.println("電梯門關(guān)閉...");
}
//電梯門關(guān)了再打開(kāi),逗你玩呢,那這個(gè)允許呀
@Override
public void open() {
super.context.setLiftState(Context.openningState); //置為門敞狀態(tài)
super.context.getLiftState().open();
}
//電梯門關(guān)了就跑,這是再正常不過(guò)了
@Override
public void run() {
super.context.setLiftState(Context.runningState); //設(shè)置為運(yùn)行狀態(tài);
super.context.getLiftState().run();
}
//電梯門關(guān)著,我就不按樓層
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState); //設(shè)置為停止?fàn)顟B(tài);
super.context.getLiftState().stop();
}
}
public class OpenningState extends LiftState {
//開(kāi)啟當(dāng)然可以關(guān)閉了,我就想測(cè)試一下電梯門開(kāi)關(guān)功能
@Override
public void close() {
//狀態(tài)修改
super.context.setLiftState(Context.closeingState);
//動(dòng)作委托為CloseState來(lái)執(zhí)行
super.context.getLiftState().close();
}
//打開(kāi)電梯門
@Override
public void open() {
System.out.println("電梯門開(kāi)啟...");
}
//門開(kāi)著電梯就想跑,這電梯,嚇?biāo)滥悖? @Override
public void run() {
//do nothing;
}
//開(kāi)門還不停止?
public void stop() {
//do nothing;
}
}
public class RunningState extends LiftState {
//電梯門關(guān)閉?這是肯定了
@Override
public void close() {
//do nothing
}
//運(yùn)行的時(shí)候開(kāi)電梯門?你瘋了!電梯不會(huì)給你開(kāi)的
@Override
public void open() {
//do nothing
}
//這是在運(yùn)行狀態(tài)下要實(shí)現(xiàn)的方法
@Override
public void run() {
System.out.println("電梯上下跑...");
}
//這個(gè)事絕對(duì)是合理的,光運(yùn)行不停止還有誰(shuí)敢做這個(gè)電梯?!估計(jì)只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.stoppingState); //環(huán)境設(shè)置為停止?fàn)顟B(tài);
super.context.getLiftState().stop();
}
}
public class StoppingState extends LiftState {
//停止?fàn)顟B(tài)關(guān)門?電梯門本來(lái)就是關(guān)著的!
@Override
public void close() {
//do nothing;
}
//停止?fàn)顟B(tài),開(kāi)門,那是要的!
@Override
public void open() {
super.context.setLiftState(Context.openningState);
super.context.getLiftState().open();
}
//停止?fàn)顟B(tài)再跑起來(lái),正常的很
@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.getLiftState().run();
}
//停止?fàn)顟B(tài)是怎么發(fā)生的呢?當(dāng)然是停止方法執(zhí)行了
@Override
public void stop() {
System.out.println("電梯停止了...");
}
}
Client:
public class Client {
public static void main(String[] args) {
Context context = new Context();
//設(shè)置電梯當(dāng)前狀態(tài)
context.setLiftState(new ClosingState());
context.open();
context.close();
context.run();
context.stop();
}
}
結(jié)果輸出:
電梯門開(kāi)啟...
電梯門關(guān)閉...
電梯上下跑...
電梯停止了...
4、Android中使用舉例
QQ加好友受限場(chǎng)景實(shí)現(xiàn)
規(guī)定:
1、每個(gè)QQ號(hào)一天最多能夠發(fā)送50個(gè)添加好友的邀請(qǐng)(防止惡意軟件頻繁添加好友),超過(guò)50個(gè)QQ號(hào)將被限制
2、同一個(gè)IP一天最多發(fā)送500好友邀請(qǐng),一旦超過(guò)了500個(gè),系統(tǒng)會(huì)自動(dòng)對(duì)這個(gè)IP限制
public interface IQQState {
public void onStateChange(String message);
}
public class QQStateNotRestriction implements IQQState {
@Override
public void onStateChange(String message) {
System.out.println("QQ加好友未受限......,"+message);
}
}
public class QQStateRestriction implements IQQState {
@Override
public void onStateChange(String message) {
System.out.println("QQ加好友受限,不能再加好友......");
}
}
public class QQContext {
private IQQState state;
// 保存好友
private Map<String, ArrayList<String>> friendNameMap = new HashMap<>();
// 好友數(shù)量
private Map<String, Integer> friendCountMap = new HashMap<>();
private List<IQQState> stateList = new ArrayList<>();
public QQContext() {
state = new QQStateNotRestriction();
stateList.add(new QQStateNotRestriction());
}
/**
* 添加好友
*
* @param userName
* @param friendName
*/
public void addFriend(String userName, String friendName) throws Exception {
Integer count = friendCountMap.get(userName);
ArrayList<String> arrayList = friendNameMap.get(userName);
if (arrayList == null) {
arrayList = new ArrayList<>();
}
arrayList.add(friendName);
friendNameMap.put(userName, arrayList);
friendCountMap.put(userName, count == null ? 1 : ++count);
StateType[] values = StateType.values();
if (count == null) count = 1;
for (StateType stateType : values) {
//篩選出了第一個(gè)
if (count <= stateType.getCount()) {
state = stateType.getClazz().newInstance();
break;
}
}
}
/**
* 發(fā)消息
*
* @param message
*/
public void sendMessage(String message) {
if (state != null) {
state.onStateChange(message);
}
}
enum StateType {
QQStateNot(5, QQStateNotRestriction.class),
QQState(6, QQStateRestriction.class);
private int count;
private Class<? extends IQQState> clazz;
StateType(int count, Class<? extends IQQState> clazz) {
this.count = count;
this.clazz = clazz;
}
public int getCount() {
return count;
}
public Class<? extends IQQState> getClazz() {
return clazz;
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
QQContext qqContext = new QQContext();
for (int i = 0; i < 6; i++) {
qqContext.addFriend("kpioneer", "Lisi" + i);
qqContext.sendMessage("我是kpioneer");
}
}
}
設(shè)定超出加好友5個(gè)的時(shí)候,受限。
結(jié)果輸出:
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友受限,不能再加好友......
5、模式總結(jié)
5.1 優(yōu)點(diǎn)
- 封裝了轉(zhuǎn)換規(guī)則。
- 枚舉可能的狀態(tài),在枚舉狀態(tài)之前需要確定狀態(tài)種類。
- 將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,并且可以方便地增加新的狀態(tài),只需要改變對(duì)象狀態(tài)即可改變對(duì)象的行為。
- 允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體,而不是某一個(gè)巨大的條件語(yǔ)句塊。
- 可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)。
5.2 缺點(diǎn)
- 狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對(duì)象的個(gè)數(shù)。
- 狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。
- 狀態(tài)模式對(duì)"開(kāi)閉原則"的支持并不太好,對(duì)于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無(wú)法切換到新增狀態(tài),而且修改某個(gè)狀態(tài)類的行為也需修改對(duì)應(yīng)類的源代碼。