嵌入式开发中最重要的当属状态模式(State),因为几乎所有的嵌入式开发都需要编写代码来响应硬件的各种状态。因此,代码中通常要使用各种分支结构来处理硬件的各种状态。
通过简单的CD播放器为例,逐步学习一下:
一、代码如下:
//
// cdplayer.c
// TestXianDaiC
//
// Created by kangxg on 2018/2/3.
// Copyright © 2018年 zy. All rights reserved.
//
#include "cdplayer.h"
typedef enum{
EV_STOP,
EV_PLAT_PAUSE,
} EventCode;
void onEvent(EventCode ec);
bool playflag;
bool pauseflag;
void initialize(){
playflag = false;
pauseflag = false;
onEvent(EV_PLAT_PAUSE);
onEvent(EV_PLAT_PAUSE);
onEvent(EV_PLAT_PAUSE);
onEvent(EV_STOP);
}
void onEvent(EventCode ec)
{
switch (ec) {
case EV_STOP:
{
if (playflag == true || pauseflag == true )
{
stopPlayer();
}
}
break;
case EV_PLAT_PAUSE:
{
if (playflag== true)
{
pausePlayer();
}
else if (pauseflag == true)
{
resumePlayer();
}
else
{
startPlayer();
}
}
break;
default:
break;
}
}
void stopPlayer()
{
pauseflag = false;
playflag = false;
printf("停止播放\n");
}
void pausePlayer()
{
pauseflag = true;
playflag = false;
printf("暂停播放\n");
}
void resumePlayer()
{
pauseflag = false;
playflag = true;
printf("恢复播放\n");
}
void startPlayer()
{
pauseflag = false;
playflag = true;
printf("开始播放\n");
}
调用方法运行:
initialize();
输出:
开始播放
暂停播放
恢复播放
停止播放
虽然上面代码满足了播放器的这些功能,但是阅读起来有些困难。代码中使用了playflag和 pauseflag 来保存播放器状态,但是一旦开始使用标志位,当程序扩展时就需要不断地增加标志位,这将导致程序越来越难以理解。而且,给标志位设值的规则也很难理解。如果另一个人在修改或是扩展代码时查看了pausePlayer函数,以暂停状态的playflag 的值为false为前提编写代码,就会导致程序在某些特定情况下无法正常工作。
再次进行更改:
//
// cdplayer.c
// TestXianDaiC
//
// Created by kangxg on 2018/2/3.
// Copyright © 2018年 zy. All rights reserved.
//
#include "cdplayer.h"
typedef enum{
EV_STOP,
EV_PLAY_PAUSE,
} EventCode;
typedef enum{
ST_IDLE,
ST_PLAY,
ST_PAUSE,
} State;
void onEvent(EventCode ec);
bool playflag;
bool pauseflag;
State state;
void initialize(){
state = ST_IDLE;
onEvent(EV_PLAY_PAUSE);
onEvent(EV_PLAY_PAUSE);
onEvent(EV_PLAY_PAUSE);
onEvent(EV_STOP);
}
void onEvent(EventCode ec)
{
switch (state) {
case ST_IDLE:
{
if (ec == EV_PLAY_PAUSE )
{
startPlayer();
}
}
break;
case ST_PLAY:
{
switch (ec) {
case EV_STOP:
{
stopPlayer();
}
break;
case EV_PLAY_PAUSE:
{
pausePlayer();
}
break;
default:
break;
}
}
break;
case ST_PAUSE:
{
switch (ec) {
case EV_STOP:
stopPlayer();
break;
case EV_PLAY_PAUSE:
resumePlayer();
default:
break;
}
}
default:
break;
}
}
void stopPlayer()
{
state = ST_IDLE;
printf("停止播放\n");
}
void pausePlayer()
{
state = ST_PAUSE;
printf("暂停播放\n");
}
void resumePlayer()
{
state = ST_PLAY;
printf("恢复播放\n");
}
void startPlayer()
{
state = ST_PLAY;
printf("开始播放\n");
}
将状态作为变量可以很好地避免代码出现意图模糊的问题。
但是这里出现了一个遗漏的问题,就是停止播放后,播放器会如何工作。
上面代码状态迁移表
二、状态模式
上面的代码已经很清晰了,但是当状态和事件增加后,onEvent函数就会变得非常庞大,这是因为该函数的代码行数与状态和事件数量的乘积成正比。另外 switch 语句中的条件分支也会变得非常复杂,代码会变得难以阅读和理解。
面向对象可以帮助我们将复杂的条件分支转变为多态。当代码中有条件分支时,使用多态性可实现面向对象的状态模式。
#include "cdplayer.h"
typedef struct State
{
const struct State *(* const stop)(const struct State * pThis);
const struct State *(* const palyOrPause)(const struct State * pThis);
}State;
static const State * pCurrentState;
static const State * ignore(const State * pThis);
static const State * startPlay(const State * pThis);
static const State * stopPlay(const State * pThis);
static const State * pausePlay(const State * pThis);
static const State * resumePlay(const State * pThis);
const State IDLE = {
//停止:按钮按下时处理
ignore,
//播放/暂停:按钮按下时处理
startPlay
};
const State PLAY = {
//停止:按钮按下时处理
stopPlay,
//播放/暂停:按钮按下时处理
pausePlay
};
const State PAUSE = {
//停止:按钮按下时处理
stopPlay,
//播放/暂停:按钮按下时处理
resumePlay
};
void initialize(){
pCurrentState = &IDLE;
}
void onStop()
{
pCurrentState = pCurrentState->stop(pCurrentState);
}
void onPlayOrPause()
{
pCurrentState = pCurrentState->palyOrPause(pCurrentState);
}
static const State * ignore(const State * pThis)
{
printf("播放状态\n");
return pCurrentState;
}
static const State * startPlay(const State * pThis)
{
printf("播放状态\n");
return &PLAY;
}
static const State * stopPlay(const State * pThis)
{
printf("初始状态\n");
return &IDLE;
}
static const State * pausePlay(const State * pThis)
{
printf("暂停状态\n");
return &PAUSE;
}
static const State * resumePlay(const State * pThis)
{
printf("恢复播放状态\n");
return &PLAY;
}
void initialize(void);
void onStop(void);
void onPlayOrPause(void);
外部调用:
initialize();
//播放
onPlayOrPause();
//暂停
onPlayOrPause();
//恢复播放
onPlayOrPause();
//停止
onStop();