State 狀態模式在 Android 多彈窗的應用

序言

最近項目的首頁彈窗進行調整,要加幾個彈窗,而且還是要按順序彈出的。原來的只有懸浮窗權限彈窗和存儲權限彈窗,用一兩個標誌位就可以解決了。現在加了隱私協議彈窗和青少年模式彈窗,變成了四個彈窗,如果還是按照原來的方法,即加標誌位解決,邏輯機會變得非常複雜,也很容易出 Bug.

經過調研,發現可以用 state 轉態模式去解決這個問題。

下面我們先看看 state 轉態模式

State 狀態態模式

意圖

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
允許一個對象在其內部狀態改變時改變它的行爲,對象看起來似乎修改了它的類

State 模式與狀態機有着類似的地方,都是從一個狀態切換到另一個狀態
在這裏插入圖片描述

State 模式的類圖
在這裏插入圖片描述
圖片來源 design-patterns: State

參與者

Context 上下文

  • 定義 Client 感興趣的接口
  • 維護一個 ConcreteState 子類的實例,這個實例是當前的 state

State 狀態

定義接口以封裝與 Context 的一個特定狀態相關的行爲.

ConcreteState 具體狀態類

每一個子類實現一個與 Context 的一個狀態相關的行爲

協作

  1. Context 將狀態相關的請求委託給當前的 ConcreteState 對象處理

  2. Context 可以將自身作爲一個參數傳遞給 state, 讓 state 可以訪問到 Context

  3. Context 是 client 使用的主要接口, 一般情況下 client 不需要直接與 state 打交道

  4. Context 或 ConcreteState 子類都可以決定 next state, 以及設置轉態轉換的條件

適用性

  1. 一個對象的行爲取決於它的狀態,並且必須在運行時刻根據狀態改變它的行爲

  2. 一個操作中含有龐大的分支條件,並且這些分支依賴該對象的狀態

實現過程

  1. 確定 Context 上下文

  2. 確定 state 的接口

  3. 繼承 state 接口,實現具體 state 類

  4. 在 Context 中,用一個成員變量指向當前的狀態,並且提供對外方法可以設置這個值

  5. 初始化一個開始的 State 並傳進 context。

State 模式在 Android 中的應用

回到我們文章開頭的問題,我們想要把彈窗順序的彈出,剛好和 state 模式中的狀態切換是一致的。 第一個彈窗彈窗後,切換到另一個狀態,下個彈窗是否彈出,完全取決於所在的狀態。這樣就可以減少一堆的標誌位判斷了。

這樣說比較抽象,可以結合下面的例子來看;

需求:

在第一個彈窗之後,點擊確認或者取消;
彈窗第二個彈窗,點擊第二個彈窗的確認或者取消,彈窗第三個彈窗;
點擊第三個彈窗,結束;

StateDialog 的設計

下面是類圖

在這裏插入圖片描述

DialogContext 是上下文,用來存儲當前 DialogState,在 nextDialogState 方法設置下個狀態

public class DialogContext {

    private BaseDialogState mCurrentDialogState;

    private Activity mActivity;

    public DialogContext(Activity activity) {
        mActivity = activity;
    }

    public void nextDialogState(BaseDialogState baseDialogState) {
        mCurrentDialogState = baseDialogState;
        mCurrentDialogState.setDialogContext(this); // 將自身作爲參數傳遞給 DialogState
        mCurrentDialogState.handle(); // 同時調用 DialogState#handle 方法
    }

    public Activity getActivity() {
        return mActivity;
    }
    
    public void onResume() {
        if (mCurrentDialogState != null){
            mCurrentDialogState.onResume();
        }
    }
}

BaseDialogState 是做彈窗的基類,提供 nextDialogState 和 handle 方法
在 handle 方法裏面進行自身邏輯的處理

public abstract class BaseDialogState {
    protected static final String TAG = "DialogState";
    protected DialogContext mDialogContext;
    protected Activity mActivity;

    public void setDialogContext(DialogContext dialogContext) {
        mDialogContext = dialogContext;
        mActivity = dialogContext.getActivity();
    }

    // 進行自身邏輯處理
    public abstract void handle();

    /**
     * 設置下一個 state
     */
    protected abstract void nextDialogState(); 
    
    public void onResume(){

    }
}

IDialogStateManager 和它的實現類 DialogStateManager 是 Activity 連接 DialogContext 的中介,相當於 Client。

public interface IDialogStateManager {

    void init(Activity activity);

    void start();

    void onResume();

}

// DialogStateManager.java
public class DialogStateManager implements IDialogStateManager{

    private DialogContext mDialogContext;

    private boolean mIsStarted; // 首次啓動

    @Override
    public void init(Activity activity) {
        mDialogContext = new DialogContext(activity);
    }

    @Override
    public void start() {
        if (mDialogContext != null && !mIsStarted){
            mIsStarted = true;
            // 設置第一個 state
            mDialogContext.nextDialogState(new DialogOneState());
        }
    }

    @Override
    public void onResume() {
        mDialogContext.onResume();
    }
}

調用過程

在 MainActivity 調用 DialogStateManager,進行管理

調用的時序圖
在這裏插入圖片描述

public class MainActivity extends AppCompatActivity {
    
    private TextView mTvStartDialog;
    private IDialogStateManager mDialogStateManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTvStartDialog = (TextView) findViewById(R.id.tv_start);
        
        // 創建 DialogStateManager 並進行初始化
        mDialogStateManager = new DialogStateManager();
        mDialogStateManager.init(this);

        mTvStartDialog.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mDialogStateManager.start(); // 啓動狀態
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        mDialogStateManager.onResume();
    }
}

最後我們看調用的效果

完整的代碼已上傳到 github

參考

  • 《設計模式 可複用面向對象軟件的基礎》第 5 章, 5.8 State (轉態)
  • design-patterns: State

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章