背景
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。
名词释义
解释器,其实就像正则表达式、SQL语言等一样,把一种特定的语言格式转换成另一个种语言格式。
C语言应用
此模式更多应该吸收它的思维,个人最常用的就是把常用的逻辑语言进行变量提取,并转换成固定的逻辑格式。
例子
现在来设计一个触摸显示屏的后台定时器功能,这个后台定时器需要实现一个功能,就是用户可以添加任意多个定时器,可以设定定时时间,定时触发条件,停止条件及执行动作。举个例子,用户可以设定当按下某个按键时,启动定时,定时每3s执行一次数据A加1的操作,当放开按键时,停止执行。
先来看下普通的实现。
#include <stdint.h>
extern uint8_t KeySta;
uint8_t Cnt = 0;
/* 中断里调用,1s调用一次 */
void TmrTick(void)
{
Cnt++;
if (Cnt > 200)
{
Cnt = 200;
}
}
/* 用户操作 */
void UserFunc(void)
{
A += 1;
}
int main(void)
{
if (1 == KeySta)
{
if (Cnt >= 3)
{
UserFunc();
Cnt = 0;
}
}
else
{
Cnt = 0;
}
}
如果这时再加一个操作呢,比如用户要求按下按键2,定时每2s执行一次A-1的操作,放开按键2时停止执行。
#include <stdint.h>
extern uint8_t KeySta[2];
uint8_t Cnt[2] = 0;
/* 中断里调用,1s调用一次 */
void TmrTick(void)
{
Cnt[0]++;
if (Cnt[0] > 200)
{
Cnt[0] = 200;
}
Cnt[1]++;
if (Cnt[1] > 200)
{
Cnt[1] = 200;
}
}
/* 用户操作 */
void UserFunc1(void)
{
A += 1;
}
void UserFunc2(void)
{
A -= 1;
}
int main(void)
{
/* 按键1的动作需求 */
if (1 == KeySta[0])
{
if (Cnt[0] >= 3)
{
UserFunc1();
Cnt[0] = 0;
}
}
else
{
Cnt[0] = 0;
}
/* 按键2的动作需求 */
if (1 == KeySta[1])
{
if (Cnt[1] >= 2)
{
UserFunc2();
Cnt[1] = 0;
}
}
else
{
Cnt[1] = 0;
}
}
如果用户再继续增加类似的按键功能,虽然每次复制代码就可以实现,但改动的地方还是很多,容易出现错误。这里我们来找找规律,上面增加的两个操作中,有哪些是不一样的?不难看出,这里触发条件、解除条件不同,定时的时间不同,执行动作也不同,但其逻辑层面上,是相同的,那我们就可以把相同的逻辑关系抽象出来固化封装。
#include <stdint.h>
/* 解释器的实现 */
void Interpreter(uint8_t tri_cond,
uint8_t time,
void (*func)(void *),
void *pt,
uint8_t *tmr_cnt)
{
if (tri_cond)
{
if (*tmr_cnt >= time)
{
*tmr_cnt = 0;
func(pt);
}
}
else
{
*tmr_cnt = 0;
}
}
extern uint8_t KeySta[2];
uint8_t Cnt[2] = 0;
/* 中断里调用,1s调用一次 */
void TmrTick(void)
{
for (uint8_t i = 0; i < 2; i++)
{
Cnt[i]++;
if (Cnt[i] > 200)
{
Cnt[i] = 200;
}
}
}
/* 用户操作 */
void UserFunc1(void *pt)
{
A += 1;
}
void UserFunc2(void *pt)
{
A -= 1;
}
int main(void)
{
Interpreter((1 == KeySta[0]),
3,
UserFunc1,
NULL,
&Cnt[0]);
Interpreter((1 == KeySta[1]),
2,
UserFunc2,
NULL,
&Cnt[1]);
return 0;
}
解释器一般可以跟表驱动很好的贴合使用,再加上表驱动,来看下上面代码的实现。
#include <stdint.h>
/* 解释器传参封装 */
struct tagInterpreterPara
{
uint8_t TriCond;
uint8_t Time;
void (*Func)(void *);
void *Pt;
uint8_t TmrCnt;
};
/* 解释器的实现 */
void Interpreter(struct tagInterpreterPara *table)
{
if (table->TriCond)
{
if (table->TmrCnt >= table->Time)
{
table->TmrCnt = 0;
table->func(table->pt);
}
}
else
{
table->TmrCnt = 0;
}
}
/********************************后续变更需要更改的地方*********************************/
extern uint8_t KeySta[2];
/* 用户操作 */
void UserFunc1(void *pt)
{
A += 1;
}
void UserFunc2(void *pt)
{
A -= 1;
}
/* 表 */
struct tagInterpreterPara UserTable[] =
{
{
KeySta[0], 3, UserFunc1, NULL, 0},
{
KeySta[1], 2, UserFunc2, NULL, 0},
};
/***************************************************************************************/
/* 中断里调用,1s调用一次 */
void TmrTick(void)
{
for (uint8_t i = 0; i < sizeof(UserTable)/sizeof(UserTable[0]); i++)
{
UserTable[i].TmrCnt++;
if (UserTable[i].TmrCnt > 200)
{
UserTable[i].TmrCnt = 200;
}
}
}
int main(void)
{
for (uint8_t i = 0; i < sizeof(UserTable)/sizeof(UserTable[0]); i++)
{
Interpreter(&UserTable[i]);
}
return 0;
}
适用范围
- 对于有固定行为模式,并且同样的行为有大量重复出现的场景下可以使用。
优势
- 对于有特定行为模式的语句,可以最大程度地减少重复性动作的编写。
劣势
- 行为模式完全固化,当需要有新的行为变化时,无法扩展。
- 转换后的语言可能跟原本的通用语言存在冲突,容易造成应用混乱。