什么是状态模式?什么时候用?
状态模式一般用于行为是状态决定的场景,不同状态下的行为也不同。适合状态比较多(3,4个以上),方法调用时需要比较多的if-else或switch-case的情况。
怎么实现状态模式?
状态模式一般会有以下对象:
- Context,环境类,内部维护State状态对象,提供更新状态等方法。
- State,状态抽象类或状态接口。是定义状态的基础,每种状态需要继承它或实现它。
- StateImpl,具体的状态实现类,每种状态都需要新建一个类,并且继承它或实现它。
状态模式重构聊天室,限制状态切换
在项目中,实现了聊天室的基础功能,但是突然聊天室需要实现限制性功能,怎样为限制呢?
看以下分析:
- 正常状态。(可以正常聊天)
- 关闭状态。(正常不会被关闭,除非被老师关闭,24小时候自动重开,或者老师手动选择恢复,这时转换到正常状态)。
- 用户发送5条消息,老师都未回复,被限制发送状态(只有老师回复才会解除,回复一次即可,以后永远不会触发该类型限制,除非老师再次关闭了聊天室,则会重新计算次数)。
可以看到,上面有2种状态,分别是正常状态、关闭状态、限制发送状态。
而这3种状态,UI上也是不一样的,UI上的点击行为也是不一样的。
- 正常状态,没有限制,没有UI阻挡输入框。
- 关闭状态,有UI阻挡输入框,展示一行文字,并有下单付费咨询室按钮,点击跳转到老师个人中心。并且有倒计时进行24小时的倒计时。
- 限制发送状态,有UI阻挡输入框,展示一行文字,并且有下单付费咨询室的按钮,点击跳转个人中心。
而关闭状态和限制发送状态,是由后端触发,并发送WebSocket消息,客户端接收到后,进行Json解析,再处理UI。所以他们是异步的,这里我们使用RecyelrView进行实现。
为什么不用Fragment?因为在进入聊天室时,就要马上拉取状态决定是否显示某种UI,如果是接口拉取成功设置Fragment,而下一秒就需要拉取另外一个接口进行设置数据到Fragment(设置倒计时)中时就会出现异步问题,Fragment还未add到Activity,就进行Find操作,是找不到的,而且也是基于我们的状态变化,关闭状态可以转换回正常状态,限制发送状态在老师回复一句话后即可解除,这里如果是Fragment的话,切换也会存在问题(一开始设置为默认状态,拉取接口状态后马上切换为限制状态时,就会出现异步问题)。
而RecyclerView的ViewType和根据Model切换条目,刚好适合我们的需求,第一RecyclerView是直接布局上add,是同步的,不存在异步问题。拉取接口后,我们设置Model到Adapter的数据集后Notify即可,后续切换其他状态,直接清空数据集再add对应状态的Model再刷新即可~
最初的实现
其实一开始的实现并不好,当时只有一种限制发送和正常状态,没有想太多,只是将代码抽取到另外一个Agent类进行操作。到了关闭状态出现时,问题就出现了。每加一个状态,提供的方法就要成倍增加,慢慢Agent类就开始膨胀,慢慢出现了“烂代码”的坏味道。
- Agent类
public class ConsultRoomBanTipAgent {
private RecyclerView vBanTipList;
private Context mContext;
private LifecycleOwner mLifecycleOwner;
private Items mItems;
private RAdapter mAdapter;
private RoomCloseBanTipViewBinder.OnCloseReOpenCallback mOnCloseReOpenCallback;
public Context getContext() {
return mContext;
}
public void attachUI(Context context, LifecycleOwner lifecycleOwner, View layout) {
mContext = context;
mLifecycleOwner = lifecycleOwner;
findView(layout);
bindView();
}
private void findView(View view) {
//限制占位布局
vBanTipList = view.findViewById(R.id.ban_tip_list);
}
private void bindView() {
//...省略RecyclerView的设置操作
vBanTipList.setAdapter(mAdapter);
}
//设置倒计时结束时的回调
public void setOnCloseReOpenCallback(RoomCloseBanTipViewBinder.OnCloseReOpenCallback onCloseReOpenCallback) {
mOnCloseReOpenCallback = onCloseReOpenCallback;
}
/**
* 显示限制提示
*/
public void changeToLimitSendMsgTip(SendMsgLimitModel model) {
mItems.clear();
mItems.add(model);
mAdapter.notifyDataSetChanged();
}
/**
* 显示老师关闭聊天室提示
*/
public void changeToTeacherCloseBanTip(RoomCloseModel model) {
mItems.clear();
mItems.add(model);
mAdapter.notifyDataSetChanged();
}
/**
* 隐藏限制布局
*/
public void removeAllBanTip() {
mItems.clear();
mAdapter.notifyDataSetChanged();
}
//...以后再更多多张状态时,这里方法会成倍增加。
}
- 状态消息监听
msgParser.registerOnReceiveMsgCallback(new SimpleOnReceiveMsgCallback() {
@Override
public void onReceiveRoomInfo(WssResponseModel<RoomInfoModel> response) {
super.onReceiveRoomInfo(response);
handleRoomInfo(response);
}
@Override
public void onReceiveSendMsgLimitMsg(WssResponseModel<SendMsgLimitModel> response) {
super.onReceiveSendMsgLimitMsg(response);
//限制发送消息,切换到限制发送状态
SendMsgLimitModel data = response.getData();
setTeacherId(data);
changeToLimitSendMsgBanTip(data);
}
@Override
public void onReceiveSendMsgRemoveLimitMsg(WssResponseModel<SendMsgLimitModel> response) {
super.onReceiveSendMsgRemoveLimitMsg(response);
//解除限制发送消息,切换到普通状态
removeAllBanTip();
}
@Override
public void onReceiveCloseRoomMsg(WssResponseModel<RoomCloseModel> response) {
super.onReceiveCloseRoomMsg(response);
//老师关闭了咨询室
if (getConsultTeacherInfo() == null) {
return;
}
//切换到关闭状态
changeToTeacherCloseBanTip(ConsultRoomOpenStatus.CLOSE,
response.getData().getRoomCloseReOpenSeconds(),
getConsultTeacherInfo().getId());
}
@Override
public void onReceiveReOpenRoomMsg(WssResponseModel<String> response) {
super.onReceiveReOpenRoomMsg(response);
//老师重新开启了咨询室,恢复普通状态
removeAllBanTip();
}
});
实现步骤
由于状态添加导致Agent类膨胀,那么使用状态模式怎样实现呢?按照上面涉及到的对象,我们开始实现:
- State接口,所有状态都需要进行实现。
public interface BanTipState {
/**
* 开始依附
*
* @param lifecycleOwner 生命周期
* @param items 列表数据集
* @param adapter 列表适配器
*/
void onAttachState(LifecycleOwner lifecycleOwner, Items items, RAdapter adapter);
/**
* 解除依附
*/
void onDetachState();
}
- State实现类
正常状态,没有UI阻挡,可以自由聊天。
public class NormalBanTipState implements BanTipState {
@Override
public void onAttachState(LifecycleOwner lifecycleOwner, Items items, RAdapter adapter) {
//没有UI阻挡,清空列表的条目即可
items.clear();
adapter.notifyDataSetChanged();
}
@Override
public void onDetachState() {
}
}
限制发送状态。老师回复一句话有,恢复到正常状态。
public class LimitSendMsgBanTipState implements BanTipState {
private SendMsgLimitModel mModel;
private SendMsgLimitBanTipViewBinder mViewBinder;
public LimitSendMsgBanTipState(SendMsgLimitModel model) {
mModel = model;
}
@Override
public void onAttachState(LifecycleOwner lifecycleOwner, Items items, RAdapter adapter) {
if (mViewBinder == null) {
mViewBinder = new SendMsgLimitBanTipViewBinder(lifecycleOwner);
adapter.register(SendMsgLimitModel.class, mViewBinder);
}
items.clear();
items.add(mModel);
adapter.notifyDataSetChanged();
}
@Override
public void onDetachState() {
}
}
关闭状态,具有倒计时,倒计时结束后恢复到正常状态
public class TeacherCloseRoomBanTipState implements BanTipState {
private RoomCloseModel mModel;
private RoomCloseBanTipViewBinder.OnCloseReOpenCallback mOnCloseReOpenCallback;
private RoomCloseBanTipViewBinder mViewBinder;
public TeacherCloseRoomBanTipState(RoomCloseModel model, RoomCloseBanTipViewBinder.OnCloseReOpenCallback onCloseReOpenCallback) {
mModel = model;
mOnCloseReOpenCallback = onCloseReOpenCallback;
}
@Override
public void onAttachState(LifecycleOwner lifecycleOwner, Items items, RAdapter adapter) {
if (mViewBinder == null) {
mViewBinder = new RoomCloseBanTipViewBinder(lifecycleOwner,
new RoomCloseBanTipViewBinder.OnCloseReOpenCallback() {
@Override
public void onArriveReOpen() {
if (mOnCloseReOpenCallback != null) {
mOnCloseReOpenCallback.onArriveReOpen();
}
}
@Override
public void onCountDownError(Throwable error) {
if (mOnCloseReOpenCallback != null) {
mOnCloseReOpenCallback.onCountDownError(error);
}
}
});
adapter.register(RoomCloseModel.class, mViewBinder);
}
items.clear();
items.add(mModel);
adapter.notifyDataSetChanged();
}
@Override
public void onDetachState() {
if (mViewBinder != null) {
mViewBinder.removeCallback();
}
mOnCloseReOpenCallback = null;
}
}
Context,环境类,管理所有状态
public class BanTipContext implements LifecycleObserver {
private RecyclerView vBanTipList;
private Context mContext;
private LifecycleOwner mLifecycleOwner;
private Items mItems;
private RAdapter mAdapter;
private BanTipState mCurrentState;
/**
* @param context 上下文
* @param lifecycleOwner 生命周期
* @param rootLayout 根布局
*/
public BanTipContext(Context context, LifecycleOwner lifecycleOwner, View rootLayout) {
mContext = context;
mLifecycleOwner = lifecycleOwner;
mCurrentState = new NormalBanTipState();
findView(rootLayout);
bindView();
mLifecycleOwner.getLifecycle().addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
protected void onLifecycleDestroy() {
if (mCurrentState != null) {
mCurrentState.onDetachState();
}
}
private void findView(View rootLayout) {
vBanTipList = rootLayout.findViewById(R.id.ban_tip_list);
}
private void bindView() {
vBanTipList.setLayoutManager(new LinearLayoutManager(mContext));
mItems = new Items();
mAdapter = new RAdapter(mItems);
vBanTipList.setAdapter(mAdapter);
}
public void setState(BanTipState newState) {
mCurrentState.onDetachState();
mCurrentState = newState;
mCurrentState.onAttachState(mLifecycleOwner, mItems, mAdapter);
}
}
消息监听,对应回调切换到对应的状态
//环境类对象
mBanTipContext = new BanTipContext(getContext(), mLifecycleOwner, vRootView);
msgParser.registerOnReceiveMsgCallback(new SimpleOnReceiveMsgCallback() {
@Override
public void onReceiveRoomInfo(WssResponseModel<RoomInfoModel> response) {
super.onReceiveRoomInfo(response);
handleRoomInfo(response);
}
@Override
public void onReceiveSendMsgLimitMsg(WssResponseModel<SendMsgLimitModel> response) {
super.onReceiveSendMsgLimitMsg(response);
//限制发送消息
if (getConsultTeacherInfo() == null) {
return;
}
SendMsgLimitModel model = response.getData();
setTeacherId(model);
//设置为限制发送消息状态
mBanTipContext.setState(new LimitSendMsgBanTipState(model));
}
@Override
public void onReceiveSendMsgRemoveLimitMsg(WssResponseModel<SendMsgLimitModel> response) {
super.onReceiveSendMsgRemoveLimitMsg(response);
//解除限制发送消息,恢复到普通状态
mBanTipContext.setState(new NormalBanTipState());
}
@Override
public void onReceiveCloseRoomMsg(WssResponseModel<RoomCloseModel> response) {
super.onReceiveCloseRoomMsg(response);
//老师关闭了咨询室,设置为关闭状态
mBanTipContext.setState(new TeacherCloseRoomBanTipState(
new RoomCloseModel(teacherUid, roomReOpenSeconds),
new RoomCloseBanTipViewBinder.OnCloseReOpenCallback() {
@Override
public void onArriveReOpen() {
removeAllBanTip();
}
@Override
public void onCountDownError(Throwable error) {
error.printStackTrace();
L.d("咨询室关闭后,重新开启倒计时出现异常: " + error.getMessage());
}
}));
}
@Override
public void onReceiveReOpenRoomMsg(WssResponseModel<String> response) {
super.onReceiveReOpenRoomMsg(response);
//老师重新开启了咨询室,恢复到正常状态
mBanTipContext.setState(new NormalBanTipState());
}
private void setTeacherId(SendMsgLimitModel model) {
model.setTeacherUid(getConsultTeacherInfo().getId());
}
});
总结
-
状态模式的优点:将状态和行为打包到具体的状态类,Context环境类进行管理,让职责更加清晰。具有相同方法,但是方法行为不同时,不同状态类进行复写,减少了if-else。
-
状态模式的缺点:设计模式一贯弊端,状态越多,子类越多。
状态模式的其他场景。
例如订单模块,未支付、已支付、待评价、已评价等状态,同样他们的行为会不一致:
- 已支付、待评价和已评价这几种状态下能够进行删除,而未支付不能。
- 待评价和已评价可以进行再次咨询或者再来一单等等。
再或者审核状态,待审核,审核成功、审核失败等。
- 待审核,可以提交审核,而审核成功不能再次提交。
- 审核失败可以修改后重新提交,而待审核和审核失败不能。
- 审核成功才能跳转到流程后的界面,而待审核和审核失败不能。
网友评论