What are state patterns? When will it be used?

The state pattern is generally used in scenarios where the behavior is determined by the state, and the behavior varies from state to state. Suitable for cases where there are many states (3 or more) and more if-else or switch-case is required for method calls.

How to implement the state pattern?

A state mode typically has the following objects:

  • Context, the environment class, maintains the State object internally, provides methods to update the State, etc.
  • State abstract class or State interface. Is the basis for defining states that each state needs to inherit or implement.
  • StateImpl, the concrete state implementation class. Each state requires creating a new class and inheriting or implementing it.

State mode reconstructs the chat room to limit state switching

In the project, the basic function of the chat room is realized, but suddenly the chat room needs to realize the restricted function, how to restrict? Take a look at this analysis:

  1. Normal status. (Can chat normally)

  1. Closed state. (Normal will not be closed, unless closed by the teacher, automatic restart 24 hours, or the teacher manually choose to restore, then switch to the normal state).

  1. If the user sends 5 messages and the teacher does not reply, the user is restricted to send status (only the teacher responds once, this type of restriction will never be triggered, unless the teacher closes the chat room again, the number of times will be recalculated).


As you can see, there are two states: normal state, closed state, and restricted send state.

And these three states, they’re also different on the UI, and the click behavior on the UI is different.

  1. Normal, no restrictions, no UI blocking the input box.
  2. In the closed state, UI blocks the input box to display a line of text, and there is a button for the consultation room of ordering and paying. Click to jump to the teacher’s personal center. And there’s a countdown that runs for 24 hours.
  3. Limit the sending status, there is UI block input box, show a line of text, and there is a button in the consultation room for ordering and paying, click to jump to the personal center.

The closed state and restricted send state are triggered by the back end and send WebSocket messages. After receiving them, the client parses Json and processes UI. So they’re asynchronous, and we’re going to implement that using RecyelrView.

Why not Fragment? When entering a chat room, you need to immediately pull the state to decide whether to display a CERTAIN UI. If the interface is successfully pulled to set the Fragment, the next second you need to pull another interface to set the Fragment. When data is added to the Fragment (set countdown), there will be asynchronous problems. Just for the Find operation, not to be found, but also based on our state changes, closed can be back to normal, limit state after a word reply teacher can be lifted, if here is fragments, switch would exist problems (initially set to the default state, pull interface switch immediately to limit state after state, Asynchrony is a problem).

And RecyclerView ViewType and switch items according to Model, just suitable for our needs, the first RecyclerView is directly add layout, is synchronous, there is no asynchronous problem. After pulling the interface, we can set the Model to Notify the Adapter data set, and then switch to other states. We can directly clear the data set and add the Model in the corresponding state and refresh it

Initial implementation

In fact, the initial implementation was not good, at that time there was only a limited send and normal state, without thinking too much, just extract the code to another Agent class for operation. The problem arises when the shutdown occurs. With each state, the provided methods multiply, and slowly the Agent class begins to swell, giving rise to the bad taste of “bad code.”

  • The Agent class
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.i.b.ban_tip_list); } private voidbindView() {/ /... Omitted RecyclerView setting operation vbantiplist. setAdapter(mAdapter); } // Set the countdown to the end of the callback public voidsetOnCloseReOpenCallback(RoomCloseBanTipViewBinder.OnCloseReOpenCallback onCloseReOpenCallback) { mOnCloseReOpenCallback = onCloseReOpenCallback; } /** * Display limits */ public void changeToLimitSendMsgTip(SendMsgLimitModel model) {mitems.clear (); mItems.add(model); mAdapter.notifyDataSetChanged(); } / display teacher close chat room message * * * * / public void changeToTeacherCloseBanTip (RoomCloseModel model) {mItems. The clear (); mItems.add(model); mAdapter.notifyDataSetChanged(); } /** * hide the restricted layout */ public voidremoveAllBanTip() { mItems.clear(); mAdapter.notifyDataSetChanged(); } / /... In the future, when we have more and more states, this method will multiply. }Copy the code
  • Status message monitoring
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); // The teacher closed the consulting roomif (getConsultTeacherInfo() == null) {
                    return; } / / switch to the closed changeToTeacherCloseBanTip (ConsultRoomOpenStatus. CLOSE to the response. The getData (). GetRoomCloseReOpenSeconds (), getConsultTeacherInfo().getId()); } @Override public void onReceiveReOpenRoomMsg(WssResponseModel<String> response) { super.onReceiveReOpenRoomMsg(response); RemoveAllBanTip (); }});Copy the code

Implementation steps

Agent class bloat due to state addition, so how to implement using state mode? Based on the objects mentioned above, we start implementing:

  • State interface, all states need to be implemented.
Public interface BanTipState {/** * start attaching ** @param lifecycleOwner lifecycle * @param Items list data set * @Param Adapter list adapter */ void onAttachState(LifecycleOwner lifecycleOwner, Items items, RAdapter adapter); /** * unattach */ void onDetachState(); }Copy the code
  • The State implementation class

Normal state, no UI block, you can chat freely.

public class NormalBanTipState implements BanTipState { @Override public void onAttachState(LifecycleOwner Module.clear (); // No UI blocking, items.clear(); adapter.notifyDataSetChanged(); } @Override public voidonDetachState() {}}Copy the code

Limit the send status. The teacher replied with a word, return to the normal state.

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() {}}Copy the code

Closed state, with countdown, after the countdown to return to the normal state

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 voidonDetachState() {
        if (mViewBinder != null) {
            mViewBinder.removeCallback();
        }
        mOnCloseReOpenCallback = null;
    }
}
Copy the code

Context, the environment class, manages all the states

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 context * @param lifecycleOwner lifecycle * @param rootLayout 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 voidbindView() {
        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); }}Copy the code

Message listening, corresponding callback to switch to the corresponding state

// Environment class object mBanTipContext = new BanTipContext(getContext(), mLifecycleOwner, vRootView); msgParser.registerOnReceiveMsgCallback(newSimpleOnReceiveMsgCallback() { @Override public void onReceiveRoomInfo(WssResponseModel<RoomInfoModel> response) { super.onReceiveRoomInfo(response); handleRoomInfo(response); } @Override public void onReceiveSendMsgLimitMsg(WssResponseModel<SendMsgLimitModel> response) { super.onReceiveSendMsgLimitMsg(response); // Restrict sending messagesif (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); // The teacher closed the consulting room, Set to closed 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("After the consulting room was closed, there was an anomaly in the re-opening countdown:"+ error.getMessage()); }})); } @Override public void onReceiveReOpenRoomMsg(WssResponseModel<String> response) { super.onReceiveReOpenRoomMsg(response); Mbantipcontext.setstate (new NormalBanTipState()); } private voidsetTeacherId(SendMsgLimitModel model) { model.setTeacherUid(getConsultTeacherInfo().getId()); }});Copy the code

conclusion

  • Advantages of the state pattern: The state and behavior are packaged into a specific state class, which is managed by the Context environment class, making responsibilities clearer. Have the same method, but the method behavior is different, different state class overwrite, reduce if-else.

  • Disadvantages of the state pattern: The design pattern has always had the disadvantage that the more states, the more subclasses.

Other scenarios for state mode.

For example, the order module, unpaid, paid, to be evaluated, evaluated, etc., also their behavior will be inconsistent:

  • Paid, to be evaluated and evaluated these states can be deleted, but not paid cannot.
  • To be evaluated and evaluated can be consulted again or another order, and so on.

Or audit status, to audit, audit success, audit failure, etc.

  • Pending audit, can be submitted for audit, and audit success cannot be submitted again.
  • Audit failure can be modified and re-submitted, while pending audit and audit failure cannot.
  • The interface after the process can be jumped only after the audit is successful, but the interface after the audit and audit failure cannot be.