序言
最近项目的首页弹窗进行调整,要加几个弹窗,而且还是要按顺序弹出的。原来的只有悬浮窗权限弹窗和存储权限弹窗,用一两个标志位就可以解决了。现在加了隐私协议弹窗和青少年模式弹窗,变成了四个弹窗,如果还是按照原来的方法,即加标志位解决,逻辑机会变得非常复杂,也很容易出 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_2.png
State 模式的类图
state_1.png参与者
Context 上下文
- 定义 Client 感兴趣的接口
- 维护一个 ConcreteState 子类的实例,这个实例是当前的 state
State 状态
定义接口以封装与 Context 的一个特定状态相关的行为.
ConcreteState 具体状态类
每一个子类实现一个与 Context 的一个状态相关的行为
协作
-
Context 将状态相关的请求委托给当前的 ConcreteState 对象处理
-
Context 可以将自身作为一个参数传递给 state, 让 state 可以访问到 Context
-
Context 是 client 使用的主要接口, 一般情况下 client 不需要直接与 state 打交道
-
Context 或 ConcreteState 子类都可以决定 next state, 以及设置转态转换的条件
适用性
-
一个对象的行为取决于它的状态,并且必须在运行时刻根据状态改变它的行为
-
一个操作中含有庞大的分支条件,并且这些分支依赖该对象的状态
实现过程
-
确定 Context 上下文
-
确定 state 的接口
-
继承 state 接口,实现具体 state 类
-
在 Context 中,用一个成员变量指向当前的状态,并且提供对外方法可以设置这个值
- 初始化一个开始的 State 并传进 context。
State 模式在 Android 中的应用
回到我们文章开头的问题,我们想要把弹窗顺序的弹出,刚好和 state 模式中的状态切换是一致的。 第一个弹窗弹窗后,切换到另一个状态,下个弹窗是否弹出,完全取决于所在的状态。这样就可以减少一堆的标志位判断了。
这样说比较抽象,可以结合下面的例子来看;
需求:
在第一个弹窗之后,点击确认或者取消;
弹窗第二个弹窗,点击第二个弹窗的确认或者取消,弹窗第三个弹窗;
点击第三个弹窗,结束;
StateDialog 的设计
下面是类图
state 模式1.pngDialogContext 是上下文,用来存储当前 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,进行管理
调用的时序图
首页弹窗的时序图1.png
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();
}
}
最后我们看调用的效果
state_dialog_demo.gif参考
- 《设计模式 可复用面向对象软件的基础》第 5 章, 5.8 State (转态)
-
qrcode.jpg