前言 :在網上有很多面向接口編程的文章與例子,特別是在面向對象編程方面的,比如JAVA裡面的接口,C++裡面的接口,還有C語言的函數指針類的接口。但是我這裡的講的面向"接口"編程與其不一樣,所以我這裡加了雙引號,避免大家說我偷換概念。我這裡的面向"接口"編程這個接口是在嵌入式中實際存在的一個物理接口。
關鍵點:面向接口編程,相當於透過現象看本質,抓住事務的共性;在程序實現中應該依賴於抽象,而不依賴於具體。
為了更加容易理解面向接口編程的思想,現在假設有一個簡單的燈光控制系統(娛樂室內燈控系統):由一個主控設備與三個子設備組成。主控設備有三個接口,每個接口由24VDC電源輸出與一個串口組成,一共4根線。每個子設備由一個接口(串口及電源共4根線)及其控制的一種燈組成,圖示如下:
重點來了
方案1、不同種類子設備子固定插入到主控固定接口(LED類固定插入接口1,普通照明類固定插入接口2,激光燈類固定插入接口3)。
方案2、不同種類子設備子插入主控的接口不固定,隨便插。
任務是:實現主控的程序功能。
針對方案1,得出的設計極有可能是面向具體對象的設計實現,即燈光類型這個對象。針對方案2得到的必定是高度抽象的面向接口方式的設計實現。從方案的優劣來看,應該極大部分人都會選擇方案2。但是對一般設計實現者(碼農)極有可能選擇方案1,因為"看起來"實現的難度小,都是固定的不是很容易嗎。下面我選擇方案2來實現這個設計。由上面假設的內容可見,它們的物理接口是一樣的四根線,分別為24V+GND+TXD+RXD,這樣子設備插入任意接口的物理基礎有了。(假如採用方案1還需要實現物理防呆的設計)
1、抽象出模塊的接口
為了適應需求的靈魂性需要對主控模塊的接口進行抽象出去基本功能需求,得到下圖所示的樣子。
由上圖及需求很容易想象到:對接口進行編程才是唯一出路,不可能針對某種類型的具體設備進行編程,否則需要對每個接口進行適配三種類型的燈控功能(因為方案2的需求)。此即為依賴於抽象,而不依賴於具體(燈光類型)。此處的抽象為主控的接口,而此處的具體的某個類型的子設備。所以要實現這個主控的程序,必須對子設備進行一定程度的抽象,其共性一定要認識得非常透切。
2、實現接口描述
現在看看每個接口的共性有那些屬性:給出代碼如下(此處給出簡單的共同屬性)
typedef struct
{
bool Inserted; //插入標誌
char name[10]; //名稱
u8 LampType; //類型
u8 Sn[10]; //序列號
u8 WorkFunc; //工作
u8 Status; //狀態
u8 NeedPower; //需求功率
u8 FuncList[10]; //功能列表
}LampInfo_t;
再給出燈類型定義(代碼如下):
typedef enum
{
eLampTypeNone=0, //類型不確定(初始類型)
eLampTypeLed=1, //LED類
eLampTypeLaser, //激光類
eLampTypeNormal, //普通類(普通照明)
}eLampType_t;
給出接口類型定義
typedef enum
{
eLampIFNone=0, //接口類型
eLampIF1=1, //接口1
eLampIF2, //接口2
eLampIF3, //接口3
}eLampIF_t;
現在再給出三個接口的描述
typedef struct
{
u8 OnlineDevAmount; //在線子設備數量
LampInfo_t IF1; //接口1
LampInfo_t IF2; //接口2
LampInfo_t IF3; //接口3
}LampIfInfo_t;
現在抽象出這幾種類型的燈光控制效果,給出代碼如下
typedef enum
{
eLampFuncNone=0, //功能
eLampFuncOpen=1, //打開
eLampFuncClose, //關閉
eLampFuncFlicker05HZ,//0.5Hz閃爍
eLampFuncFlicker1HZ,//1Hz閃爍
eLampFuncFlicker2HZ,//2Hz閃爍
eLampFuncFlicker3HZ,//3Hz閃爍
eLampFuncFlicker5HZ,//5Hz閃爍
eLampFuncFlicker10HZ,//10Hz閃爍
//其他燈光效果(子設備是這個功能列表的子集)
}eLampFuncList_t;
3、 實現接口描述功能初始化
//燈描述初始化
Static void LampInfoInit(LampInfo_t *LampInfo)
{
LampInfo->Inserted=FALSE; //插入標誌
memcpy(LampInfo->name,"default",10); //名稱
LampInfo->LampType=eLampTypeNone; //類型
memset(LampInfo->Sn,0,10); //序列號
LampInfo->WorkFunc=eLampFuncNone; //工作
LampInfo->Status=0; //狀態
LampInfo->NeedPower=0; //需求功率
memset(LampInfo->FuncList,0,10); //功能列表
}
//燈接口描述初始
void LampIfInfoInit(LampIfInfo_t *Lamp)
{
Lamp->OnlineDevAmount=0; //在線子設備數量
LampInfoInit(&Lamp->IF1); //接口1
LampInfoInit(&Lamp->IF2); //接口2
LampInfoInit(&Lamp->IF3); //接口3
}
4、實現接口常規功能
4.1、設置與獲取子設備插入接口標誌
void SetLampInterFaceInsertedFlag(LampIfInfo_t *Lamp,eLampIF_t IfNumber,bool FLAG)
{
switch(IfNumber)
{
case eLampIF1:Lamp->IF1.Inserted=FLAG; //設置接口1插入標誌
break;
case eLampIF2:Lamp->IF2.Inserted=FLAG; //設置接口2插入標誌
break;
case eLampIF3:Lamp->IF3.Inserted=FLAG; //設置接口3插入標誌
break;
default:
break;
}
}
bool GetLampInterFaceInsertedFlag(LampIfInfo_t *Lamp,eLampIF_t IfNumber,bool FLAG)
{
bool result=FALSE;
switch(IfNumber)
{
case eLampIF1:result=Lamp->IF1.Inserted;
break;
case eLampIF2:result=Lamp->IF2.Inserted;
break;
case eLampIF3:result=Lamp->IF3.Inserted;
break;
default:
break;
}
return result;
}
4.2、獲取子設備在線數量
u8 GetLampOnlineAmount(LampIfInfo_t *Lamp)
{
Lamp->OnlineDevAmount=0; //在線設備數量清零
if(Lamp->IF1.Inserted==TRUE)Lamp->OnlineDevAmount+=1; //接口1在線總數+1
if(Lamp->IF2.Inserted==TRUE)Lamp->OnlineDevAmount+=1; //接口2在線總數+1
if(Lamp->IF3.Inserted==TRUE)Lamp->OnlineDevAmount+=1; //接口3在線總數+1
return Lamp->OnlineDevAmount; //返回在線子設備數量
}
4.3、設置與獲取接口上子設備的名稱
void SetLampDeviceName(LampIfInfo_t *Lamp,eLampIF_t IfNumber,char *DevName)
{
switch(IfNumber)
{
case eLampIF1:memcpy(Lamp->IF1.name,DevName,strlen(DevName)); //拷貝接口1名稱
break;
case eLampIF2:memcpy(Lamp->IF2.name,DevName,strlen(DevName)); //拷貝接口2名稱
break;
case eLampIF3:memcpy(Lamp->IF3.name,DevName,strlen(DevName)); //拷貝接口3名稱
break;
default:
break;
}
}
bool GetLampDeviceName(LampIfInfo_t *Lamp,eLampIF_t IfNumber,char *DevName)
{
bool result=TRUE;
switch(IfNumber)
{
case eLampIF1:memcpy(DevName,Lamp->IF1.name,10); //拷貝接口1名稱
break;
case eLampIF2:memcpy(DevName,Lamp->IF2.name,10); //拷貝接口2名稱
break;
case eLampIF3:memcpy(DevName,Lamp->IF3.name,10); //拷貝接口3名稱
break;
default:result=FALSE;
break;
}
return result;
}
4.4、設置與獲取子設備類型
void SetLampDeviceType(LampIfInfo_t *Lamp,eLampIF_t IfNumber,eLampType_t Type)
{
switch(IfNumber)
{
case eLampIF1:Lamp->IF1.LampType=Type;
break;
case eLampIF2:Lamp->IF2.LampType=Type;
break;
case eLampIF3:Lamp->IF3.LampType=Type;
break;
default:
break;
}
}
eLampType_t GetLampDeviceType(LampIfInfo_t *Lamp,eLampIF_t IfNumber)
{
eLampType_t Type;
switch(IfNumber)
{
case eLampIF1:Type=Lamp->IF1.LampType;
break;
case eLampIF2:Type=Lamp->IF2.LampType;
break;
case eLampIF3:Type=Lamp->IF3.LampType;
break;
default:Type=eLampTypeNone;
break;
}
return Type;
}
4.5、其他功能
忽略部分代碼:包括子設備序列號,需求功率,功能列表,當前工作模式(功能列表中的一項),設備狀態。
5、工作流程
以上模塊代碼是主控模塊對於子設備的功能抽象描述,此為面向“接口”編程的基礎。其他工作流程均依據這個基礎。現在看看面向“接口”編程的主控工作流程。如下圖所示。
由上圖可見接口描述字是面向“接口”編程的關鍵,只有高度抽象出其描述字才能設計出好的產品。實現了接口描述字相關功能,方案2的設計基本就實現了。
總結:
面向“接口”編程是一個編程思想,它依賴的是對象抽象出來的共同屬性部分,它不依賴於具體的某一個對象。就如上面的例子一樣,只有這樣高度抽象出子設備的共同屬性,才能實現子設備插入任意物理接口的需求。當然實際產品中的物理接口可能還有其他形式,比如增加一個插拔檢測引腳,或者其他IO信號。無論物理接口形式如何變化,只要心裡有面向“接口”編程的思想,就能抽象出實物的共同屬性。另外面向“接口”編程可以演化為面向“接口”設計,包括硬件設計,結構設計。只有硬件與結構按照面向“接口”設計之後,才能給編程實現面向“接口”編程提供實現基礎。
大家有什麼不同看法可以留言交流,再次謝謝。
閱讀更多 西小岑 的文章