「设计模式(三) - 状态模式与StateMachine」

「设计模式(三) - 状态模式与StateMachine」
一、抱怨不能解决问题,但思考可以

作为开发,最头痛的无非就是需求的变动了,毕竟产品的思维太过于“超前”;频繁的变动有时候真的让人捶胸顿足。明明想好的设计可能重新修改。但是面对同样的问题为什么有的同学就能游刃有余呢?承认别人优秀很难,但不得不服的是,别的同学在设计之初确实考虑的很多,包括各种可能性,系统被设计的很健壮,拥有优异的扩展性。提醒自己面对棘手的问题时,多思考思考总能找到解决的办法,也算是一种成长的方式吧。

二、状态模式 State Pattern

对象或者系统是具有状态属性的,并且存在功能的行为;状态的不同决定了不同的行为。这些行为的集合是相互独立的,并不能相互替换,这点是区别于策略模式的;本质上是根据状态选择行为。像最常见的购物车状态的变化,购物车空、继续加购、清空购物车等等。

三、结构组成
  • 需要抽象的状态State:通常定义了或者说约束了实现类的行为
  • 需要具体的状态实现类Concrete State:对State中定义的行为进行了具体的实现,并且行为是对应相应的主干台的。当然作为状态模式重要组成部分,具体的实现类可以有很多。
  • 上下文Context:持有了State的引用,当前的状态,这个很好理解,因为Context决定了状态的切换。
  • 结构类图:
    状态模式UML
四、代码实现
1. 设计一个简单的交通方式选择系统

出行方式的选择很自然的跟距离是有关系,根据距离的长度选择合适出行方式,像不行、骑自行车、轿车、高铁、飞机等等。当然这仅仅是从距离一个纬度来考虑,然而方式的选择可以是综合考虑的,舒适度,土豪程度等。

  • 状态的抽象接口State,出行的具体细节不考虑
/**
 * Created by Sai
 * on: 11/01/2022 16:22.
 * Description:
 */
public interface TravelStyle {
  String description();

  void handle(long distance);
}
  • 出行方式的具体实现类-步行
/**
 * Created by Sai
 * on: 11/01/2022 16:32.
 * Description:
 */
public class WalkMode implements TravelStyle {
    @Override
    public String description() {
        return "Travel by walking, just for 2km";
    }

    @Override
    public void handle(long distance) {
        System.out.println(distance + "km Waling is enough");
        System.out.println("------------------------------>");
    }
}
  • 出行方式的具体实现类-自行车
/**
 * Created by Sai
 * on: 11/01/2022 16:28.
 * Description:
 */
public class BikeMode implements TravelStyle {
    @Override
    public String description() {
        return "Travel by biking, just for 4km";
    }

    @Override
    public void handle(long distance) {
      System.out.println(distance + "km Ride a Bike is nice");
      System.out.println("------------------------------>");
    }
}
  • 出行方式的具体实现类-轿车
/**
 * Created by Sai
 * on: 11/01/2022 16:33.
 * Description:
 */
public class CarMode implements TravelStyle {
    @Override
    public String description() {
        return "Travel by driving car, just for 100km";
    }

    @Override
    public void handle(long distance) {
        System.out.println(distance + "km Drive a car!");
        System.out.println("------------------------------>");
    }
}
  • 出行方式的具体实现类-飞机
/**
 * Created by Sai
 * on: 11/01/2022 16:34.
 * Description:
 */
public class AirPlaneMode implements TravelStyle {
    @Override
    public String description() {
        return "Travel by plane, just for 2000km";
    }

    @Override
    public void handle(long distance) {
        System.out.println(distance + "km is too long so just by airplane");
        System.out.println("------------------------------>");
    }
}
  • “上下文”Context决定了状态的切换
/**
 * Created by Sai
 * on: 11/01/2022 16:36.
 * Description:
 */
public class TravelModeSystem {
  private static final long WALK_MODE_MAX_VALUE = 1L;
  private static final long BIKE_MODE_MIN_VALUE = 4L;
  private static final long CAR_MODE_MIN_VALUE = 100L;
  private static final long PLANE_MODE_MIN_VALUE = 1000L;

  private long distance;
  private TravelStyle travelStyle = null;

  public TravelModeSystem() {}

  public long getDistance() {
    return distance;
  }

  public void setDistance(long distance) {
    this.distance = distance;
  }

  public TravelStyle getTravelStyle() {
    return travelStyle;
  }

  public void setTravelStyle(TravelStyle travelStyle) {
    this.travelStyle = travelStyle;
  }

  public void selectSuitableMode(long distance) {
    if (BIKE_MODE_MIN_VALUE > distance && distance > WALK_MODE_MAX_VALUE) {
      travelStyle = new WalkMode();
    } else if (distance < CAR_MODE_MIN_VALUE) {
      travelStyle = new BikeMode();
    } else if (distance < PLANE_MODE_MIN_VALUE) {
      travelStyle = new CarMode();
    } else {
      travelStyle = new AirPlaneMode();
    }
    travelStyle.handle(distance);
  }
  • 测试Demo
/**
 * Created by Sai
 * on: 11/01/2022 17:19.
 * Description:
 */
public class Demo {
  public static void main(String[] args) {
    TravelModeSystem system = new TravelModeSystem();
    system.selectSuitableMode(1);
    system.selectSuitableMode(3);
    system.selectSuitableMode(105);
    system.selectSuitableMode(1200);
  }
}
  • 打印信息
1km Ride a Bike is nice
------------------------------>
3km Waling is enough
------------------------------>
105km Drive a car!
------------------------------>
1200km is too long so just by airplane
------------------------------>

Process finished with exit code 0
2.一些总结

“出行选择系统”,通过对出行的距离这一单一维度将出行的方式的行为与出行的状态(距离长度)隔离开。把特定的状态对应的行为放入相应的对象之中。客户端不用关心特定对象的具体实现而只需要关系状态的切换即可,拥有了更好的扩展性,只需要在State的约束下实现新的类加入到系统中。状态的切换更加透明清晰。当然缺点同样也是很明显,状态新增也伴随着系统类的膨胀,其次新增的类需要加入到系统中必然会设计修改源码违反了开闭原则。当然没有完美的设计模式,具体业务具体考虑,也没有一尘不变的系统,适合的才是最好的。

拆分了状态模块使粒度更小,简化了逻辑控制;分离了状态与行为,层级清晰;更易于扩展新的状态;状态的切换更透明。同时也伴随着系统的膨胀问题;难免会设计修改源码的弊端,对开闭原则不是很友好。

五、实际问题

目前手头开发的系统(TO B)中,就存在对购物车状态的控制与切换,而购物车不同的状态不仅仅展示的文案不同也会存在一些其他操作。主要的状态有加购、待支付、挂取单、空状态。

//购物车状态的抽象接口
public interface IState {
   void onOption(IStateController controller, FragmentBinding binding);
}
//状态的控制接口
public interface IStateController {
   /**
    * 获取当前状态
    * @return
    */
   IState currentState();
   /**
    * 设置新状态
    * @param state
    */
   void updateState(IState state);
}
//几个状态的具体实现类
//加购状态
public class AddProductState implements IState {
   public AddProductState(FragmentBinding binding) {
     //ui update ...
     binding.textView.setText(R.string.string_pending_order);
     binding.llClear.setEnabled(true);
     binding.llPack.setVisibility(View.VISIBLE);
     //...
    }

    @Override
    public void onOption(IStateController controller, FragmentBinding binding) {
     controller.updateState(new PreparePayState(binding));
     EventBusUtils.post(true, ShopCartType.DISH_CART);
    }
}
//空状态
public class CartEmptyState implements IState {
    public CartEmptyState(FragmentBinding binding) {
      //ui update...
    }

    @Override
    public void onOption(IStateController controller, FragmentBinding binding) {
      ToastUtil.showNoProduct(binding.getRoot().getContext(),
            ResourceUtil.getString(XXX.getApp(), R.string.string_cart_empty_title),
            ToastUtil.DURATION_SHORT);
    }
}
//....
//购物车状态管理类里实现了IStateController接口
@Override
public IState currentState() {
    return this.cartState;
}

@Override
public void updateState(IState state) {
    this.cartState = state;
    cartVM.queryHangCount(this);
}
//....

并没有直接设置确定的上下文Context,而是以为接口IStateController来指定,当然这也是根据业务来决定的,存在同一个购物车页面但是不同控制器(Context),这样设计灵活度稍微好一点。而其他的还是同UML类图设计的一样。

六、思考并合理变形

GoF推荐的23种设计模式很经典,但是随着业务的发展,生搬硬套显然是不合适的,合理的选择配合实际业务作出自己的改变以适合系统。

  • 典型的当对象或者系统的属性(状态)决定其行为时可以考虑使用状态模式State Pattern
  • 优化现有的,对于有多重分支的并且可以抽象出状态State与之对应的行为Operation时则应考虑使用状态模式重构。
七、Android FW层的“状态模式” - 分层状态机StateMachine

Android 源码中(Frameworks)中包含了状态机的使用,作为上层App开发平时是接触不到的(Google将这部分实现对上层是隐藏了{@hide}),包含了三个基本的组成类,IStateSateStateMachine;在底层中对StateMachine的使用还是比较多的。像蓝牙模块、WI-FI模块等算是比较典型的例子。

  • 这里的StateMachine其实也是基于State状态模式的思想实现的,注释将其解释为分层状态机也即HSM,Hierarchical State Machine区别于Android的硬件安全模块HSM(Hardware Security Module)

  • 原文注释

    The state machine defined here is a hierarchical state machine which processes messages and can have states arranged hierarchically.
    //此处定义的状态机其实是一个分层状态机的概念,处理消息且可以将状态分层排列
    
  • 作为上层如果想要实现自己的状态机,那么只需要将对应的三个类拷贝到自己的项目中即可。
    导入文件关系图

  • IState完整代码(基于Android 9.0`),源码下载地址IState

/**
 * Created by Sai
 * on 2022/1/11 09:47.
 * Description:
 */
public interface IState {
    /**
     * Returned by processMessage to indicate the message was processed.
     */
    static final boolean HANDLED = true;
    /**
     * Returned by processMessage to indicate the message was NOT processed.
     */
    static final boolean NOT_HANDLED = false;
    /**
     * Called when a state is entered.
     */
    void enter();
    /**
     * Called when a state is exited.
     */
    void exit();
    /**
     * Called when a message is to be processed by the
     * state machine.
     *
     * This routine is never reentered thus no synchronization
     * is needed as only one processMessage method will ever be
     * executing within a state machine at any given time. This
     * does mean that processing by this routine must be completed
     * as expeditiously as possible as no subsequent messages will
     * be processed until this routine returns.
     *
     * @param msg to process
     * @return HANDLED if processing has completed and NOT_HANDLED
     *         if the message wasn't processed.
     */
    boolean processMessage(Message msg);
    /**
     * Name of State for debugging purposes.
     *
     * @return name of state.
     */
    String getName();
}

状态的抽象接口是比较简单的,包含enter、exit、processMessage、getName几个方法。进入、退出方法是每个实现类都有的方法,以便状态的切换和记录。

  • State,接口IState的默认实现,也是要实现分层状态机StateMachine的组成部分之一,不同的状态继承自State,下载地址State,完整代码:
/**
 * Created by Sai
 * on 2022/1/11 09:49.
 * Description:
 */
public class State implements IState {
    /**
     * Constructor
     */
    protected State() {
    }

    /* (non-Javadoc)
     * @see com.android.internal.util.IState#enter()
     */
    @Override
    public void enter() {
    }

    /* (non-Javadoc)
     * @see com.android.internal.util.IState#exit()
     */
    @Override
    public void exit() {
    }
    /* (non-Javadoc)
     * @see com.android.internal.util.IState#processMessage(android.os.Message)
     */

    @Override
    public boolean processMessage(Message msg) {
        return false;
    }

    /**
     * Name of State for debugging purposes.
     *
     * This default implementation returns the class name, returning
     * the instance name would better in cases where a State class
     * is used for multiple states. But normally there is one class per
     * state and the class name is sufficient and easy to get. You may
     * want to provide a setName or some other mechanism for setting
     * another name if the class name is not appropriate.
     */

    @Override
    public String getName() {
        String name = getClass().getName();
        int lastDollar = name.lastIndexOf('$');
        return name.substring(lastDollar + 1);
    }
}
  • 分层状态机StateMachine,关键实现类,无论是作为底层还是上层,要想定制自己的状态机都应继承StateMachine;例如Frameworks层的WI-FI管理模块WifiStateMachineStateMachine完整代码只有一千多行,结构还是比较清晰的。抽取主要实现方法:
public class StateMachine {
  //处理状态的标识
  public static final boolean HANDLED = true;
  public static final boolean NOT_HANDLED = false;
  
  /** Message.what value when quitting 状态机退出时消息体标志位*/
  private static final int SM_QUIT_CMD = -1;
  /** Message.what value when initializing 状态机初始化时消息体标志位*/
  private static final int SM_INIT_CMD = -2;
  //状态机的消息驱动的核心内部静态类,实际上是一个自定义的Handler
  private SmHandler mSmHandler;
  
  //几个构造函数
  protected StateMachine(String name) {
    //....
    Looper looper = mSmThread.getLooper();
    initStateMachine(name, looper);
   }
  protected StateMachine(String name, Looper looper) {
    initStateMachine(name, looper);
  }
  protected StateMachine(String name, Handler handler) {
    initStateMachine(name, handler.getLooper());
  }
  
  private void initStateMachine(String name, Looper looper) {
    mSmHandler = new SmHandler(looper, this);
  }
}

几个构造函数的实现,表明了状态机的创建是离不开Looper的,即使不指定Looper,内部的构造方法,会通过实例化时所在的线程获取一个Looper对象。如果在主线程中实例化,那么使用的即为Main Lopper,同样的子线程即为子线程中Looper。这点还是很有意思的。

StateMachine中的重要静态内部类SmHandler,其实就是一个自定义的Handler,而Handler机制相信都是已经好呢了解了,作为一个消息处理中心,Looper正是这个处理消息的传送带。这也解释了,构造函数中为什么一定会取一个Looper对象。

private static class SmHandler extends Handler {
  /** true if StateMachine has quit */
  private boolean mHasQuit = false;
  private static final Object mSmHandlerObj = new Object();
  /** The current message */
  private Message mMsg;
  /** Stack used to manage the current hierarchy of states */
  private StateInfo mStateStack[];
  /** Top of mStateStack */
  private int mStateStackTopIndex = -1;
  private int mTempStateStackCount;
  /** A temporary stack used to manage the state stack */
  private StateInfo mTempStateStack[];
  /** State used when state machine is halted 即停止状态*/
  private HaltingState mHaltingState = new HaltingState();
  /** State used when state machine is quitting 退出状态*/
  private QuittingState mQuittingState = new QuittingState();
  /** Reference to the StateMachine */
  private StateMachine mSm;
  /** The map of all of the states in the state machine */
  private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();
  //.....省略....
  private SmHandler(Looper looper, StateMachine sm) {
    super(looper);
    mSm = sm;
    addState(mHaltingState, null);
    addState(mQuittingState, null);
  }
}

重点关注SmHandler中的消息处理方法handleMessage()

  @Override
  public final void handleMessage(Message msg) {
    if (!mHasQuit) {
      if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
        mSm.onPreHandleMessage(msg);
      }
      if (mDbg) mSm.log("handleMessage: E msg.what=" + msg.what);
      /** Save the current message */
      mMsg = msg;
      /** State that processed the message */
      State msgProcessedState = null;
      if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {
        /** Normal path */
        msgProcessedState = processMsg(msg);
      } else if (!mIsConstructionCompleted
          && (mMsg.what == SM_INIT_CMD)
          && (mMsg.obj == mSmHandlerObj)) {
        /** Initial one time path. */
        mIsConstructionCompleted = true;
        invokeEnterMethods(0);
      } else {
        throw new RuntimeException(
            "StateMachine.handleMessage: " + "The start method not called, received msg: " + msg);
      }
      performTransitions(msgProcessedState, msg);
      // We need to check if mSm == null here as we could be quitting.
      if (mDbg && mSm != null) mSm.log("handleMessage: X");
      if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {
        mSm.onPostHandleMessage(msg);
      }
    }
  }

注释还是比较容易理解的,代码不是很复杂,主要的作用就是通过Handler消息处理机制,根据不同的状态码选择执行具体的状态State实现类。当然源码模块下WifiStateMachine实现细节很多,作为上层开发如果有精力或者有这方面的需求可以认真仔细研究。参考具体的实现,如果接触的是车载相关的应该会有所帮助。

八、官方Demo
  • 完整代码
/**
 * Created by Sai
 * on 2022/1/11 11:45.
 * Description:
 */
public class SaiHsm extends StateMachine {
  	//状态码
    public static final int CMD_1 = 1;
    public static final int CMD_2 = 2;
    public static final int CMD_3 = 3;
    public static final int CMD_4 = 4;
    public static final int CMD_5 = 5;
    public static final String TAG = SaiHsm.class.getSimpleName();
  
    P1 mP1 = new P1();
    S1 mS1 = new S1();
    S2 mS2 = new S2();
    P2 mP2 = new P2();

    protected SaiHsm(String name) {
        super(name);
        //关联相关的状态
        addState(mP1);
        addState(mS1, mP1);
        addState(mS2, mP1);
        addState(mP2);
        setInitialState(mS1);
    }

    public static SaiHsm makeSaiHsm() {
        SaiHsm saiHsm = new SaiHsm("Sai");
        //开启状态机
        saiHsm.start();
        return saiHsm;
    }

    @Override
    protected void onHalting() {
        Log.d(TAG, "halting");
        synchronized (this) {
            this.notifyAll();
        }
    }

    class P1 extends State {
        @Override
        public void enter() {
            Log.d(TAG, "mP1.enter");
            Log.d(TAG, "do something.....");
        }

        @Override
        public void exit() {
            Log.d(TAG, "mP1.exit");
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean retVal;
            Log.d("State", "mP1.processMessage what=" + msg.what);
            switch (msg.what) {
                case CMD_2:
                    sendMessage(obtainMessage(CMD_3));
                    deferMessage(msg);
                    transitionTo(mS2);
                    retVal = HANDLED;
                    break;
                default:
                    retVal = NOT_HANDLED;
                    break;
            }
            return retVal;
        }
    }

    class S1 extends State {
        @Override
        public void enter() {
            Log.d(TAG, "mS1.enter");
        }

        @Override
        public void exit() {
            Log.d(TAG, "mS1.exit");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.d(TAG, "S1.processMessage what=" + msg.what);
            if (msg.what == CMD_1) {
                transitionTo(mS1);
                return HANDLED;
            } else {
                // Let parent process all other messages
                return NOT_HANDLED;
            }
        }
    }

    class S2 extends State {
        @Override
        public void enter() {
            Log.d(TAG, "mS2.enter");
        }

        @Override
        public boolean processMessage(Message message) {
            boolean retVal;
            Log.d(TAG, "mS2.processMessage what=" + message.what);
            switch (message.what) {
                case (CMD_2):
                    sendMessage(obtainMessage(CMD_4));
                    retVal = HANDLED;
                    break;
                case (CMD_3):
                    deferMessage(message);
                    transitionTo(mP2);
                    retVal = HANDLED;
                    break;
                default:
                    retVal = NOT_HANDLED;
                    break;
            }
            return retVal;
        }

        @Override
        public void exit() {
            Log.d(TAG, "mS2.exit");
        }
    }

    class P2 extends State {
        @Override
        public void enter() {
            log("mP2.enter");
            sendMessage(obtainMessage(CMD_5));
        }

        @Override
        public boolean processMessage(Message message) {
            log("P2.processMessage what=" + message.what);
            switch (message.what) {
                case (CMD_3):
                case (CMD_4):
                    break;
                case (CMD_5):
                    transitionToHaltingState();
                    break;
            }
            return HANDLED;
        }

        @Override
        public void exit() {
            log("mP2.exit");
        }
    }
}
  • 打印的信息
2022-01-12 01:15:29.931 9198-9223/com.statemachinetest D/SaiHsm: mP1.enter
2022-01-12 01:15:29.932 9198-9223/com.statemachinetest D/SaiHsm: do something.....
2022-01-12 01:15:29.932 9198-9223/com.statemachinetest D/SaiHsm: mS1.enter
2022-01-12 01:15:29.932 9198-9223/com.statemachinetest D/SaiHsm: S1.processMessage what=1
2022-01-12 01:15:29.932 9198-9223/com.statemachinetest D/SaiHsm: mS1.exit
2022-01-12 01:15:29.933 9198-9223/com.statemachinetest D/SaiHsm: mS1.enter
2022-01-12 01:15:29.933 9198-9223/com.statemachinetest D/SaiHsm: S1.processMessage what=2
2022-01-12 01:15:29.933 9198-9223/com.statemachinetest D/SaiHsm: mS1.exit
2022-01-12 01:15:29.933 9198-9223/com.statemachinetest D/SaiHsm: mS2.enter
2022-01-12 01:15:29.933 9198-9223/com.statemachinetest D/SaiHsm: mS2.processMessage what=2
2022-01-12 01:15:29.934 9198-9223/com.statemachinetest D/SaiHsm: mS2.processMessage what=3
2022-01-12 01:15:29.934 9198-9223/com.statemachinetest D/SaiHsm: mS2.exit
2022-01-12 01:15:29.934 9198-9223/com.statemachinetest D/SaiHsm: mP1.exit
2022-01-12 01:15:29.939 9198-9223/com.statemachinetest D/SaiHsm: halting
  • 有精力的情况还是很值得花精力去研究下这个分层状态机StateMachine的,实现的很优雅🐶🐶🐶。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐二狗呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值