函数指针,领域语言,乱弹
实现一个模块ct,该模块的需求如下:
ct的收到数据后,进行处理,并将结果输出可能有三个不同的地方,我们称之为AM,BM,CM(称为模块A, 模块B, 模块C)。
并且在模块的运行过程中,有可能改变输出的位置(丛输出到模块A变更为输出到模块B)。
模块A, 模块B, 模块C会给ct发送反馈信息,收到这些信息之后,ct将进行Lcd显示,Led点灯,buzzer鸣动。根据反馈的信息不同,以上三个信息也会不同。
Ct在未收到反馈之前,再次收到数据的话,将这些数据丢弃。(也有可能缓存起来,收到反馈之后,再处理缓存的数据)
设计时的考虑:
1, 由于输出有3个不同的地方,我们很自然的想到抽象。将输出的动作抽象为统一的接口。这样ct模块的绝大多数处理逻辑将不会意识到具体的输出地。当然,一部分的处理逻辑需要了解并控制输出到哪个模块。但是,我们想得到的是集中控制。这种控制权要集中在一个或者尽肯能少函数中。这种控制逻辑越混乱,代码出错的可能性就越高。
2, Ct在收到数据时,要进行Lcd显示,Led点灯,buzzer鸣动处理。并且根据反馈的信息不同,以上三个信息也会不。由于这种处理是相当容易变化的,(lcd显示什么内容?哪个led需要点亮,需要buzzer多长时间?等),所以我们不能将这种易变的,细节的内容放进代码中。而应该将其放到外部配置文件或数据中。
3, Ct模块很明显有根据状态的不同,作不同的处理的逻辑。状态模式能够排上用场。
实现时的考虑:
1, 抽象出输出功能的接口。很自然,函数指针。在更改输出位置时,只需要重新设置该指针就可以了。
2, Lcd显示,Led点灯,buzzer鸣动的控制信息,我们很容易想到xml配置文件,xml文件稍嫌复杂,有点儿大材小用了。不过这种简单的功能我们可以使用表达力更强的领域语言来描述。也就是说,我们站在更高级的抽象层上进行思考,设计,实现。形如DSV(Delimiter-separated values)字符串的格式就可以胜任该工作。有变化时,直接修改DSV字符串。体现了将细节放进元数据,将抽象放进代码的思想。
3, Ct模块的状态。有基于表的状态迁移的实现和基于Robert C. Martin的三层状态机实现。考虑到仅有两个状态,就不用劳驾复杂的三层状态机了。体现了实现复杂度和问题域间的一种权衡。
下面是基于这种考虑的一个架子性实现。
#include <string.h>
typedef unsigned char UC ;
typedef unsigned short US ;
typedef unsigned long UL ;
typedef int sMessage ;
typedef enum
{
CT_STS_IDLE = 0 ,
CT_STS_PROC
} CT_STS ;
typedef UL ( * PFCMEVICEHANDLER ) ( sMessage * pMessage ) ;
static PFCMEVICEHANDLER gpfcmeviceHandlers ;
static UL am_Handler ( sMessage * pMessage ) ;
static UL bm_Handler ( sMessage * pMessage ) ;
static UL cm_Handler ( sMessage * pMessage ) ;
static PFCMEVICEHANDLER gpfnAMHandlers = am_Handler ;
static PFCMEVICEHANDLER gpfnBMHandlers = bm_Handler ;
static PFCMEVICEHANDLER gpfnCMHandlers = cm_Handler ;
typedef PFCMEVICEHANDLER PFNMATRIXHANDLER ;
/* [IDLE .. PROCESSING] */
static UL idle_Handler ( sMessage * pMessage ) ;
static UL proc_Handler ( sMessage * pMessage ) ;
static PFNMATRIXHANDLER gMatrixHandler[] =
{ idle_Handler , proc_Handler } ;
static CT_STS gSts ;
/* LCD、LED、ブザーの処理*/
/* 次のフォーマットで表す*/
/* [LCD MSG]: [LED OK/NG]: [TIME]: [BUZZA ON]: [OFF]: [CNT] */
#define LLB_SEPARATOR ": "
#define LLB_SCENARIO_1 "NULL: OK: 100: 200: 0: 1"
#define LLB_SCENARIO_2 "MSG1: NG: 100: 200: 200: 3"
#define LLB_SCENARIO_3 "MSG2: NG: 100: 200: 200: 3"
typedef struct LcdLedBuzzaCtrlInfo
{
UL ulLcdMessage ;
UC ucLedType ;
UC ucLedTimeMs ;
US usReserve1 ;
US usBuzzaOnTimeMs ;
US usBuzzaOffTimeMs ;
US usBuzzaCnt ;
US usReserve2 ;
} LcdLedBuzzaCtrlInfo ;
static LcdLedBuzzaCtrlInfo parsedl( const char * pdl ) ;
static void processdl( LcdLedBuzzaCtrlInfo * info ) ;
int _tmain(int argc, _TCHAR* argv[])
{
gSts = CT_STS_IDLE ;
sMessage s ;
// default
gpfcmeviceHandlers = gpfnCMHandlers ;
// sometime change to am
gpfcmeviceHandlers = gpfnAMHandlers ;
// sometime change to bm
gpfcmeviceHandlers = gpfnBMHandlers ;
// gpfcmeviceHandlersを利用して、操作を行い
gpfcmeviceHandlers( &s ) ;
processdl ( &parsedl(LLB_SCENARIO_3) ) ;
return 0;
}
UL am_Handler ( sMessage * pMessage ) { printf("am_Handler") ; return 0 ; }
UL bm_Handler ( sMessage * pMessage ) { printf("bm_Handler") ; return 0 ; }
UL cm_Handler ( sMessage * pMessage ) { printf("cm_Handler") ; return 0 ; }
UL idle_Handler ( sMessage * pMessage )
{
// pMessageが入力メッセージの場合、
// gpfcmeviceHandlersを呼び出す
// Procに遷移
return 0 ;
}
UL proc_Handler ( sMessage * pMessage )
{
// pMessageが入力メッセージの場合、捨てる
// pMessageがレスポンスメッセージの場合、Idleに遷移
return 0 ;
}
LcdLedBuzzaCtrlInfo parsedl( const char * pdl )
{
LcdLedBuzzaCtrlInfo info ;
char * pLCDMsg ;
char * pLEDType ;
char * pLEDTime ;
char * pBuzOnTime ;
char * pBuzOffTime ;
char * pBuzCnt ;
char scenario[128] ;
memset ( &info , 0 , sizeof(info) ) ;
strcpy( scenario , pdl ) ;
pLCDMsg = strtok( scenario , LLB_SEPARATOR ) ;
pLEDType = strtok( NULL , LLB_SEPARATOR ) ;
pLEDTime = strtok( NULL , LLB_SEPARATOR ) ;
pBuzOnTime = strtok( NULL , LLB_SEPARATOR ) ;
pBuzOffTime = strtok( NULL , LLB_SEPARATOR ) ;
pBuzCnt = strtok( NULL , LLB_SEPARATOR ) ;
// check
if ( (!pLCDMsg) ||
(!pLEDType) || (!pLEDTime) ||
(!pBuzOnTime) || (!pBuzOffTime) || (!pBuzCnt)
)
{
printf("error/n") ;
return info ;
}
printf("LCD->%s, LED->%s:%s, Buzza->%s:%s:%s/n" ,
pLCDMsg ,
pLEDType , pLEDTime ,
pBuzOnTime , pBuzOffTime , pBuzCnt ) ;
return info ;
}
void processdl( LcdLedBuzzaCtrlInfo * info )
{ }