Android 架構師之路14 設計模式之狀態模式

Android 架構師之路 目錄

1、狀態模式概念

1.1 介紹

在狀態模式(State Pattern)中,類的行為是基于它的狀態改變的。這種類型的設計模式屬于行為型模式。
在狀態模式中,我們創建表示各種狀態的對象和一個行為隨著狀態對象改變而改變的 context 對象。

1.2 定義

允許對象在內部狀態發生改變時改變它的行為,對象看起來好像修改了它的類。

1.3 使用場景
  • 行為隨狀態改變而改變的場景
    例如權限設計,人員的狀態不同即時執行相同的行為結果也會不同,在這種情況下需要考慮使用狀態模式。

  • 條件、分支判斷語句的替代者
    在程序中大量使用switch語句或者if判斷語句會導致程序結構不清晰,邏輯混亂,使用狀態模式可以很好地避免這一問題,它通過擴展子類實現了條件的判斷處理。

2、狀態模式UML類圖

狀態模式UML類圖

角色如下:

  • 環境類(Context): 定義客戶感興趣的接口。維護一個ConcreteState子類的實例,這個實例定義當前狀態。
  • 抽象狀態類(State): 定義一個接口以封裝與Context的一個特定狀態相關的行為。
  • 具體狀態類(ConcreteState): 每一子類實現一個與Context的一個狀態相關的行為。

3、狀態模式實現

Context:
public class Context {
    //定義出所有的電梯狀態
    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();
    //定一個當前電梯狀態
    private LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //把當前的環境通知到各個實現類中
        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{
    //定義一個環境角色,也就是封裝狀態的變換引起的功能變化
    protected Context context;

    public void setContext(Context _context) {
        this.context = _context;
    }

    //首先電梯門開啟動作
    public abstract void open();

    //電梯門有開啟,那當然也就有關閉了
    public abstract void close();

    //電梯要能上能下,跑起來
    public abstract void run();

    //電梯還要能停下來,停不下來那就扯淡了
    public abstract void stop();
}
ConcreteState:
public class ClosingState extends LiftState {
    //電梯門關閉,這是關閉狀態要實現的動作
    @Override
    public void close() {
        System.out.println("電梯門關閉...");
    }

    //電梯門關了再打開,逗你玩呢,那這個允許呀
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState); //置為門敞狀態
        super.context.getLiftState().open();
    }

    //電梯門關了就跑,這是再正常不過了
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState); //設置為運行狀態;
        super.context.getLiftState().run();
    }

    //電梯門關著,我就不按樓層
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState); //設置為停止狀態;
        super.context.getLiftState().stop();
    }
}

public class OpenningState extends LiftState {
    //開啟當然可以關閉了,我就想測試一下電梯門開關功能
    @Override
    public void close() {
        //狀態修改
        super.context.setLiftState(Context.closeingState);
        //動作委托為CloseState來執行
        super.context.getLiftState().close();
    }

    //打開電梯門
    @Override
    public void open() {
        System.out.println("電梯門開啟...");
    }

    //門開著電梯就想跑,這電梯,嚇死你!
    @Override
    public void run() {
        //do nothing;
    }

    //開門還不停止?
    public void stop() {
        //do nothing;
    }
}
public class RunningState extends LiftState {
    //電梯門關閉?這是肯定了
    @Override
    public void close() {
        //do nothing
    }
    //運行的時候開電梯門?你瘋了!電梯不會給你開的
    @Override
    public void open() {
        //do nothing
    }
    //這是在運行狀態下要實現的方法
    @Override
    public void run() {
        System.out.println("電梯上下跑...");
    }
    //這個事絕對是合理的,光運行不停止還有誰敢做這個電梯?!估計只有上帝了
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState); //環境設置為停止狀態;
        super.context.getLiftState().stop();
    }
}
public class StoppingState extends LiftState {
    //停止狀態關門?電梯門本來就是關著的!
    @Override
    public void close() {
        //do nothing;
    }

    //停止狀態,開門,那是要的!
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState);
        super.context.getLiftState().open();
    }

    //停止狀態再跑起來,正常的很
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState);
        super.context.getLiftState().run();
    }

    //停止狀態是怎么發生的呢?當然是停止方法執行了
    @Override
    public void stop() {
        System.out.println("電梯停止了...");
    }
}
Client:
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        //設置電梯當前狀態
        context.setLiftState(new ClosingState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

結果輸出:

電梯門開啟...
電梯門關閉...
電梯上下跑...
電梯停止了...

4、Android中使用舉例

QQ加好友受限場景實現
規定:
1、每個QQ號一天最多能夠發送50個添加好友的邀請(防止惡意軟件頻繁添加好友),超過50個QQ號將被限制
2、同一個IP一天最多發送500好友邀請,一旦超過了500個,系統會自動對這個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<>();
    // 好友數量
    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) {
            //篩選出了第一個
            if (count <= stateType.getCount()) {
                state = stateType.getClazz().newInstance();
                break;
            }
        }
    }

    /**
     * 發消息
     *
     * @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");
        }

    }
}

設定超出加好友5個的時候,受限。
結果輸出:

QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友未受限......,我是kpioneer
QQ加好友受限,不能再加好友......

5、模式總結

5.1 優點
  • 封裝了轉換規則。
  • 枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
  • 將所有與某個狀態有關的行為放到一個類中,并且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
  • 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
  • 可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
5.2 缺點
  • 狀態模式的使用必然會增加系統類和對象的個數。
  • 狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。
  • 狀態模式對"開閉原則"的支持并不太好,對于可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的源代碼。
特別感謝:

Dream
JAVA_DIRECTION

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容