美文网首页设计模式
设计模式-状态模式

设计模式-状态模式

作者: h2coder | 来源:发表于2020-10-15 00:29 被阅读0次

什么是状态模式?什么时候用?

状态模式一般用于行为是状态决定的场景,不同状态下的行为也不同。适合状态比较多(3,4个以上),方法调用时需要比较多的if-else或switch-case的情况。

怎么实现状态模式?

状态模式一般会有以下对象:

  • Context,环境类,内部维护State状态对象,提供更新状态等方法。
  • State,状态抽象类或状态接口。是定义状态的基础,每种状态需要继承它或实现它。
  • StateImpl,具体的状态实现类,每种状态都需要新建一个类,并且继承它或实现它。

状态模式重构聊天室,限制状态切换

在项目中,实现了聊天室的基础功能,但是突然聊天室需要实现限制性功能,怎样为限制呢?
看以下分析:

  1. 正常状态。(可以正常聊天)
正常状态.png
  1. 关闭状态。(正常不会被关闭,除非被老师关闭,24小时候自动重开,或者老师手动选择恢复,这时转换到正常状态)。
关闭状态.png
  1. 用户发送5条消息,老师都未回复,被限制发送状态(只有老师回复才会解除,回复一次即可,以后永远不会触发该类型限制,除非老师再次关闭了聊天室,则会重新计算次数)。
限制发送状态.png

可以看到,上面有2种状态,分别是正常状态、关闭状态、限制发送状态。

而这3种状态,UI上也是不一样的,UI上的点击行为也是不一样的。

  1. 正常状态,没有限制,没有UI阻挡输入框。
  2. 关闭状态,有UI阻挡输入框,展示一行文字,并有下单付费咨询室按钮,点击跳转到老师个人中心。并且有倒计时进行24小时的倒计时。
  3. 限制发送状态,有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。

  • 状态模式的缺点:设计模式一贯弊端,状态越多,子类越多。

状态模式的其他场景。

例如订单模块,未支付、已支付、待评价、已评价等状态,同样他们的行为会不一致:

  • 已支付、待评价和已评价这几种状态下能够进行删除,而未支付不能。
  • 待评价和已评价可以进行再次咨询或者再来一单等等。

再或者审核状态,待审核,审核成功、审核失败等。

  • 待审核,可以提交审核,而审核成功不能再次提交。
  • 审核失败可以修改后重新提交,而待审核和审核失败不能。
  • 审核成功才能跳转到流程后的界面,而待审核和审核失败不能。

相关文章

  • 设计模式-状态模式

    设计模式-状态模式 设计模式 状态模式的关键是区分事物内部的状态

  • 设计模式——状态模式

    设计模式——状态模式 在状态模式中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。 优点: 减少...

  • 设计模式-状态设计模式

    1.定义 对于某个操作,由于其状态的不同,表现出的行为会不同(如遥控器进行音量增加的操作,在电视是开机状态下是可以...

  • 设计模式——状态模式

    前言 设计模式是指导一个程序猿以更好的姿态处理一些问题,而不再像刚学编程的我们,只会使用if-else分支语句,或...

  • 设计模式--状态模式

    基本常识:策略模式与状态模式是双胞胎,在出生时才分开。 假设公司有个糖果机,1当糖果机由糖果,投入25分钱,转动曲...

  • 设计模式——状态模式

    在阎宏博士的《JAVA与模式》一书中开头是这样描述状态(State)模式的:状态模式,又称状态对象模式(Patte...

  • 设计模式《状态模式》

    引言   上一节我们说了策略模式。这一节我们讲讲策略模式的双胞胎弟弟:状态模式,这个模式大家可能不常见,也不常用,...

  • 设计模式——状态模式

    定义 状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行...

  • 设计模式 - 状态模式

    模式定义 允许一个对象在其内部状态发生改变时改变它的行为。对象看起来似乎修改了它的类。 状态模式(State Pa...

  • 设计模式 ——— 状态模式

    STATE(状态) ———— 对象行为型模式 意图 允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改...

网友评论

    本文标题:设计模式-状态模式

    本文链接:https://www.haomeiwen.com/subject/sxompktx.html