Android 架構(gòu)師之路14 設(shè)計(jì)模式之狀態(tài)模式

Android 架構(gòu)師之路 目錄

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類圖

狀態(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)類的源代碼。
特別感謝:

Dream
JAVA_DIRECTION

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容