目录
1.什么是状态机
2. 生活中的状态机
3. 工程中的应用
4. 发散思考
1.什么是状态机
■1.1 有向图
- 有向图是指由定点和边构成的集合,我们常用G(E, V)来表示一个有向图,其中定点之间由有向边进行连接就构成了有向图。有向图在实际生活中有很多实际的意义,比如拓扑排序(简单说一下就是不断的去掉没有入度的节点)以及在网络寻址上迪杰斯特拉算法以及图数据库都是有向图的应用。换一个角度来看这个问题,有向图仿佛就是对一种实际场景的抽象。
■1.2 状态机与有向图
-
状态机之间的转换其实和有向图有着紧密的关系,打一个比方来理解状态机,一个人在一个地图上行走,地图上有若干个城市,他从一个起点出发根据自己的喜好选择不同的路,从而走到不同的城市。这个例子中包含了两个重要的信息一个是城市一个是路,那么分别对应的是有向图中的定点和边,那么在状态机中的就是状态和转变。
-
所以这么来看,要想在实际开发中利用好状态机模式,第一步其实就是设计好自己的状态机转化图,只有合理的设计了状态转化的逻辑才可以让代码的逻辑更贱简单清晰。换句话说,当你在一个简单的地图上找到一个目的地肯定是要比在一个复杂的地图上容易的多。
■1.3 Google中的Android是怎么做的?(mediaplayer)
■1.4 状态机的几个重要概念
-
State 状态:
一个状态机至少要包含两个状态。例如上面自动门的例子,有 open 和 closed 两个状态。 -
Event 事件:
事件就是执行某个操作的触发条件或者口令。对于自动门,“按下开门按钮”就是一个事件。 -
Action 动作:
事件发生以后要执行动作。例如事件是“按开门按钮”,动作是“开门”。编程的时候,一个 Action 一般就对应一个函数。 -
Transition 变换:
也就是从一个状态变化为另一个状态。例如“开门过程”就是一个变换当然了概念总是规定的,在实际的工程中我们可能需要根据实际的需要来调整我们具体的实现。
状态,程序在运行时,仅仅允许存在一种状态,这个例子中就是Idle
状态的转变,事件的发生往往会触发状态的变化,对于这个例子,
setDataSourece就是一个事件,在这个事件下Mediaplayer
进行了状态的切换,Idle -> Initialized
往往一个状态机的设置都会包含一个起始态和终态
■1.5 场景与好处
最常见的应用场景就是游戏中人物角色的控制,比如控制一个士兵(射击,跑动和跳跃等),当人数多的时候
还可以采用分层状态机来进行控制。
那么使用状态机有哪些好处呢?
1. 当一个程序有多个状态时,规范了状态机的状态转换,避免了一些引入一些复杂的判断逻辑。
2. 规范了程序在不同状态下所能提供的能力。
3. 在能力上可以进行横向扩展,提供新的状态来完善现有逻辑
■1.6 总结与回顾
- 把状态机模型引入到实际工程中时候,最重要的就是确定好整个状态机的有向图,确定好有哪些状态,事件以及动作。
2. 生活中的状态机
■2.1 本节引言
- 其实在生活中有很多状态机的例子,我想最简单可能就是灯泡了吧,设想一下一个简单的电灯泡,他可能存在几个状态:OFF -> LOW -> MEDIUM -> HIGH -> OFF。本节的内容就是通过一个例子把状态机的编程思想介绍给大家。
下面将会一步一步的对灯泡的例子进行优化,来把它整理成状态机设计模式的代码。
■2.2 第一次编码
-
在这一次编码的时候我们定义了一个枚举类型包含了所有的灯泡状态同时也定义了一个状态转换表。状态转换表具体的定义了每一状态可以转变的下一个状态,避免了错误的状态的转换。这可能也是最简单的状态机了,在类中定义一个状态,根据不同的事件进行切换。
-
但是在如果我们把状态从枚举变量里面抽出来呢?这是我们要做的第一步,将每一个状态写成一个独立的类。但是同时我们要注意一个问题,状态类必须是单例的,因为每一次切换的时候我们可能还要切换回上一次的相同的状态,我们没有必要再重新去申请释放内存。
■2.3 第二次编码
■2.4 总结与回顾
- 状态机不外乎就是状态的定义和切换,对于一些简单的例子可能使用switch就可以完成相关编码。对于一些复杂的例子我们可能需要抽象出具体状态和业务实体,以便于业务的扩展,但这并一定是绝对的。
- 看了这个例子,我想大家应该对状态机有了初步的认识,至少不会纠结在第一节中那些无聊的概念上了。 那么在实际工程上我们应该怎么样才能结合状态机的设计模型呢?这个问题我花了不少时间去思考,查阅了相关的代码,也去翻阅了Android中mediaplayer的实现,令我惊讶的是安卓的源码并没有将状态机封装的那么抽象,并没有将每一个状态抽象成一个一个单独的类,这种做法最大的一个好处就是避免了大量状态类的定义,但是不代表不可以这样去做,下一节我将尝试用一个通用的工具类来完成一个“mediaplayer”的开发,并且给大家演示一下实际效果,做进一步的讨论。
3. 工程中的应用
- 第一节的时候我们提及了andriod的mediaplayer那么我们来用状态机模拟一个复杂的场景。先看
demo演示
- 源码
- 类图带补充
#include "tinyfsm.hpp"
#include <iostream>class Idle;
class End;
class Error;
class Initialized;
class Prepared;
class Started;
class Stoped;
class Completed;
class Paused;
class Preparing;#define EVENT_CODE_BASE 0
#define EVENT_SET_DATA_SOURCE EVENT_CODE_BASE + 1
#define EVENT_SET_PREPARE_ASYNC EVENT_CODE_BASE + 2
#define EVENT_SET_RESET EVENT_CODE_BASE + 3#define EVENT_INVAILED_INPUT EVENT_CODE_BASE - 1struct MedieEvent : tinyfsm::Event {int code;
};class MediaPlayer {
public:private:friend class Idle;friend class Idle;friend class End;friend class Error;friend class Initialized;friend class Prepared;friend class Started;friend class Stoped;friend class Completed;friend class Paused;void setDataSource(MedieEvent* event) {std::cout << "MediaPlayer setDataSource working" << std::endl;};void prepareAync(MedieEvent* event) {std::cout << "MediaPlayer prepareAync working" << std::endl;};void resetReasource(MedieEvent* event) {std::cout << "MediaPlayer resetReasource working" << std::endl;};int sourceCount;
};struct SetDataSource : MedieEvent
{SetDataSource(MediaPlayer* pMedia):m_pMediaPlayer(pMedia){code = EVENT_SET_DATA_SOURCE;}MediaPlayer* m_pMediaPlayer;
};struct PreparedAsync : MedieEvent {PreparedAsync(MediaPlayer* pMedia) :m_pMediaPlayer(pMedia) {code = EVENT_SET_PREPARE_ASYNC;}MediaPlayer* m_pMediaPlayer;
};struct InviledInput : MedieEvent
{InviledInput(MediaPlayer* pMedia) :m_pMediaPlayer(pMedia) {code = EVENT_INVAILED_INPUT;}MediaPlayer* m_pMediaPlayer;
};struct Reset :MedieEvent {Reset(MediaPlayer* pMedia) :m_pMediaPlayer(pMedia) {code = EVENT_SET_RESET;}MediaPlayer* m_pMediaPlayer;
};struct MediaState : tinyfsm::Fsm<MediaState> {virtual void react(MedieEvent* ) { };// alternative: enforce handling of Toggle in all states (pure virtual)//virtual void react(Toggle const &) = 0;virtual void entry(MediaPlayer* pM) { }; /* entry actions in some states */virtual void exit(MediaPlayer* pM) { }; /* no exit actions */
};//_state_instance 由于这个里面模板类和static 的原因,这些类都是单例的
class Error : public MediaState {
public:void react(MedieEvent* event) override{if (nullptr == event)return;switch (event->code){case EVENT_INVAILED_INPUT: {std::cout << "Error: your input wrong! what should I do !" << std::endl;break;}case EVENT_SET_RESET: {std::cout << "Error: EVENT_SET_RESET working" << std::endl;Reset* resetEvent = static_cast<Reset*>(event);if (nullptr == resetEvent)return;resetEvent->m_pMediaPlayer->resetReasource(resetEvent);transit<Idle>(resetEvent->m_pMediaPlayer);break;}default:std::cout << "Error: current status can't execute your command" << std::endl;break;}}void entry(MediaPlayer* pM) override {std::cout << "Error: entry" << std::endl;//do something about mepM->sourceCount = 0;}void exit(MediaPlayer* pM) override {std::cout << "Error: exit" << std::endl;}
};class Idle : public MediaState {
public:void react(MedieEvent* event) override{std::cout << "Idle: react" << std::endl;if (nullptr == event)return;if (event->code < 0) {// TODO (songxufei) : 定义error的base类InviledInput* invaledEvent = static_cast<InviledInput*>(event);//transmit to error statetransit<Error>(invaledEvent->m_pMediaPlayer);//do our repair workdispatch<MedieEvent>(event);return;}switch (event->code){case EVENT_SET_DATA_SOURCE: {std::cout << "Idle: setDataSource working" << std::endl;SetDataSource* eventSetSource = static_cast<SetDataSource*>(event);if (nullptr == eventSetSource)return;eventSetSource->m_pMediaPlayer->setDataSource(eventSetSource);transit<Initialized>(eventSetSource->m_pMediaPlayer);break;}default:std::cout << "Error: current status can't execute your command" << std::endl;break;}MediaState::react(event);}void entry(MediaPlayer* pM) override{std::cout << "Idle: entry" << std::endl;//do something about mepM->sourceCount = 0;}void exit(MediaPlayer* pM) override{ std::cout << "Idle: exit" << std::endl;}
};class Initialized : public MediaState {
public:void react(MedieEvent* event) override {std::cout << "Initialized: react" << std::endl;if (nullptr == event)return;if (event->code < 0) {// TODO (songxufei) : 定义error的base类InviledInput* invaledEvent = static_cast<InviledInput*>(event);//transmit to error statetransit<Error>(invaledEvent->m_pMediaPlayer);//do our repair workdispatch<MedieEvent>(event);return;}switch (event->code){case EVENT_SET_PREPARE_ASYNC: {std::cout << "Initialized: prepare async working" << std::endl;PreparedAsync* preEvent = static_cast<PreparedAsync*>(event);if (nullptr == preEvent ) {std::cout << "Initialized: prepare async error" << std::endl;return;}preEvent->m_pMediaPlayer->prepareAync(preEvent);//transmist 带入指针便于操作相应资源transit<Preparing>(preEvent->m_pMediaPlayer);break;}default:std::cout << "Initialized: current status can't execute your command" << std::endl;break;}}void entry(MediaPlayer* pM) override {std::cout << "Initialized: entry" << std::endl;}void exit(MediaPlayer* pM) override {std::cout << "Initialized: exit" << std::endl;}
};class Preparing : public MediaState {
public:void react(MedieEvent* event) override {std::cout << "Preparing: react" << std::endl;if (nullptr == event)return;if (event->code < 0) {// TODO (songxufei) : 定义error的base类InviledInput* invaledEvent = static_cast<InviledInput*>(event);//transmit to error statetransit<Error>(invaledEvent->m_pMediaPlayer);//do our repair workdispatch<MedieEvent>(event);return;}switch (event->code){default:std::cout << "Preparing: current status can't execute your command" << std::endl;break;}}void entry(MediaPlayer* pM) override {std::cout << "Preparing: entry" << std::endl;}void exit(MediaPlayer* pM) override {std::cout << "Preparing: exit" << std::endl;}
};//init state with idle
FSM_INITIAL_STATE(MediaState, Idle)using fsm_handle = MediaState;//只有使用模板类是不同的地址
template<typename X>
class C {
public:static int a;
};
template<typename X>
int C<X>::a = 0;class D : public C<D> {};int main()
{printf("##1 %ld ", (long)&(C<int>::a));printf("##2 %ld ", (long)&(D::a));MediaPlayer* player = new MediaPlayer();fsm_handle::start(player);std::cout << "EVENT_SET_DATA_SOURCE : 1 " << std::endl ;std::cout << "EVENT_SET_PREPARE_ASYNC : 2 " << std::endl;std::cout << "EVENT_SET_RESET : 3 " << std::endl;while (1){int n;std::cout << std::endl << "1 - 100=Event id, 0=Quit ? ";std::cin >> n;switch (n) {case EVENT_SET_DATA_SOURCE: {SetDataSource* event = new SetDataSource(player);fsm_handle::dispatch<MedieEvent>(event);std::cout << "deleteing event resource " << std::endl;delete event;break;}case EVENT_SET_PREPARE_ASYNC: {PreparedAsync* event = new PreparedAsync(player);fsm_handle::dispatch<MedieEvent>(event);std::cout << "deleteing event resource " << std::endl;delete event;break;}case EVENT_SET_RESET: {Reset* event = new Reset(player);fsm_handle::dispatch<MedieEvent>(event);std::cout << "deleteing event resource " << std::endl;delete event;break;}case 0:goto del;default: {std::cout << "> Invalid input" << std::endl;InviledInput* event = new InviledInput(player);fsm_handle::dispatch<MedieEvent>(event);delete event;break;}}}
del:delete player;return 0;
}
- 再看封装之前应该先温习一些C++的知识
-
模板的嵌套从属类型
用于声明模板类中的的类
例如
template
void fun(const T& proto ,typename T::const_iterator it); -
std::is_same
可判断两个类型是否一样,特别在模板里面,在不清楚模板的参数时,此功能可以对一些特定的参数类型进行特
殊的处理。 -
Constexpr 关键字
告诉编译器进行编译时计算
- 源码
namespace tinyfsm
{// --------------------------------------------------------------------------struct Event { };// --------------------------------------------------------------------------#ifdef TINYFSM_NOSTDLIB// remove dependency on standard library (silent fail!).// useful in conjunction with -nostdlib option, e.g. if your compiler// does not provide a standard library.// NOTE: this silently disables all static_assert() calls below!template<typename F, typename S>struct is_same_fsm { static constexpr bool value = true; };
#else//这里是一个继承// check if both fsm and state class share same fsmtypetemplate<typename F, typename S>struct is_same_fsm : std::is_same< typename F::fsmtype, typename S::fsmtype > { };#endiftemplate<typename S>struct _state_instance{using value_type = S;using type = _state_instance<S>;//这样做相当于每一个类都是一个单利类static S value;};//这里是定义了一下一个静态变量template<typename S>typename _state_instance<S>::value_type _state_instance<S>::value;// --------------------------------------------------------------------------template<typename F>class Fsm{public:using fsmtype = Fsm<F>;using state_ptr_t = F*;static state_ptr_t current_state_ptr;// public, leaving ability to access state instance (e.g. on reset)template<typename S>static constexpr S& state(void) {static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine");return _state_instance<S>::value;}template<typename S>static constexpr bool is_in_state(void) {static_assert(is_same_fsm<F, S>::value, "accessing state of different state machine");return current_state_ptr == &_state_instance<S>::value;}/// state machine functionspublic:// explicitely specialized in FSM_INITIAL_STATE macrostatic void set_initial_state();static void reset() { };template<typename P>static void enter(P* p) {current_state_ptr->entry(p);}static void enter() {current_state_ptr->entry();}static void start() {set_initial_state();enter();}// This S is user base class pointtemplate<typename P>static void start(P* p) {set_initial_state();enter(p);}template<typename E>static void dispatch(E const& event) {current_state_ptr->react(event);}template<typename E>static void dispatch(E* const event) {current_state_ptr->react(event);}/// state transition functionsprotected:static std::mutex mutexTransmit;template<typename S>void transit(void) {std::unique_lock<std::mutex> lk(mutexTransmit);static_assert(is_same_fsm<F, S>::value, "transit to different state machine");current_state_ptr->exit();current_state_ptr = &_state_instance<S>::value;current_state_ptr->entry();}// add this for our stitucation, add by sxftemplate<typename S, typename M>void transit(M* pM) {std::unique_lock<std::mutex> lk(mutexTransmit);static_assert(is_same_fsm<F, S>::value, "transit to different state machine");current_state_ptr->exit(pM);current_state_ptr = &_state_instance<S>::value;current_state_ptr->entry(pM);}template<typename S, typename ActionFunction>void transit(ActionFunction action_function) {std::unique_lock<std::mutex> lk(mutexTransmit);static_assert(is_same_fsm<F, S>::value, "transit to different state machine");current_state_ptr->exit();// NOTE: we get into deep trouble if the action_function sends a new event.// TODO: implement a mechanism to check for reentrancyaction_function();current_state_ptr = &_state_instance<S>::value;current_state_ptr->entry();}template<typename S, typename ActionFunction, typename ConditionFunction>void transit(ActionFunction action_function, ConditionFunction condition_function) {if (condition_function()) {transit<S>(action_function);}}};//初始化了一个F* 的静态变量template<typename F>typename Fsm<F>::state_ptr_t Fsm<F>::current_state_ptr;template<typename F>std::mutex Fsm<F> ::mutexTransmit;// --------------------------------------------------------------------------template<typename... FF>struct FsmList;template<> struct FsmList<> {static void set_initial_state() { }static void reset() { }static void enter() { }template<typename E>static void dispatch(E const&) { }};template<typename F, typename... FF>struct FsmList<F, FF...>{using fsmtype = Fsm<F>;static void set_initial_state() {fsmtype::set_initial_state();FsmList<FF...>::set_initial_state();}static void reset() {F::reset();FsmList<FF...>::reset();}static void enter() {fsmtype::enter();FsmList<FF...>::enter();}static void start() {set_initial_state();enter();}template<typename E>static void dispatch(E const& event) {fsmtype::template dispatch<E>(event);FsmList<FF...>::template dispatch<E>(event);}};// --------------------------------------------------------------------------template<typename... SS> struct StateList;template<> struct StateList<> {static void reset() { }};template<typename S, typename... SS>struct StateList<S, SS...>{static void reset() {//_state_instance<S>::value.exit();_state_instance<S>::value = S();StateList<SS...>::reset();}};// --------------------------------------------------------------------------template<typename F>struct MooreMachine : tinyfsm::Fsm<F>{virtual void entry(void) { }; /* entry actions in some states */void exit(void) { }; /* no exit actions */};template<typename F>struct MealyMachine : tinyfsm::Fsm<F>{// input actions are modeled in react():// - conditional dependent of event type or payload// - transit<>(ActionFunction)void entry(void) { }; /* no entry actions */void exit(void) { }; /* no exit actions */};} /* namespace tinyfsm */#define FSM_INITIAL_STATE(_FSM, _STATE) \
namespace tinyfsm { \template<> void Fsm< _FSM >::set_initial_state(void) { \current_state_ptr = &_state_instance< _STATE >::value; \} \
}
4. 发散思考
■4.1 面向消息编程
本文的开始,我介绍了有向图的一些相关内容,有向图是一个很有趣的数据结构,它似乎模拟了我们生活中的很多场景,当我们把每一个定点看做一个状态,那么它就是我们的状态图。那么接下来我想介绍的就是把每一个定点看做一个协程一个具体的人把它们应用在编程中会是什么样?业务的驱动完全有消息来完成,没有锁的限制,这就是Carl Hewitt在1973年提出的Actor模型,也是我个人一直很喜欢的一种编程模型,可以将Actor想象成面向对象编程语言中的对象实例,不同的是Actor的状态不能直接读取和修改,方法也不能直接调用。Actor只能通过消息传递的方式与外界通信。每个参与者存在一个代表本身的地址,但只能向该地址发送消息。如下图。