「设计模式(三) - 状态模式与StateMachine」
一、抱怨不能解决问题,但思考可以
作为开发,最头痛的无非就是需求的变动了,毕竟产品的思维太过于“超前”;频繁的变动有时候真的让人捶胸顿足。明明想好的设计可能重新修改。但是面对同样的问题为什么有的同学就能游刃有余呢?承认别人优秀很难,但不得不服的是,别的同学在设计之初确实考虑的很多,包括各种可能性,系统被设计的很健壮,拥有优异的扩展性。提醒自己面对棘手的问题时,多思考思考总能找到解决的办法,也算是一种成长的方式吧。
二、状态模式 State Pattern
对象或者系统是具有状态属性的,并且存在功能的行为;状态的不同决定了不同的行为。这些行为的集合是相互独立的,并不能相互替换,这点是区别于策略模式的;本质上是根据状态选择行为。像最常见的购物车状态的变化,购物车空、继续加购、清空购物车等等。
三、结构组成
- 需要抽象的状态
State
:通常定义了或者说约束了实现类的行为
。 - 需要具体的状态实现类
Concrete State
:对State
中定义的行为进行了具体的实现,并且行为是对应相应的主干台的。当然作为状态模式重要组成部分,具体的实现类可以有很多。 - 上下文
Context
:持有了State
的引用,当前的状态,这个很好理解,因为Context
决定了状态的切换。 - 结构类图:
四、代码实现
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}),包含了三个基本的组成类,
IState
、Sate
、StateMachine
;在底层中对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
管理模块WifiStateMachine。StateMachine完整代码只有一千多行,结构还是比较清晰的。抽取主要实现方法:
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
的,实现的很优雅🐶🐶🐶。