加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長(zhǎng)期合作伙伴
立即加入
  • 正文
    • 4 多態(tài)
    • 5 小節(jié)
  • 推薦器件
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請(qǐng)入駐 產(chǎn)業(yè)圖譜

嵌入式軟件開發(fā)的對(duì)象在哪(下)

05/15 10:50
355
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

接上集《嵌入式軟件開發(fā)的對(duì)象在哪(上)》繼續(xù)...

4 多態(tài)

多態(tài)字面含義就是具有“多種形式”。從調(diào)用者的角度看對(duì)象,會(huì)發(fā)現(xiàn)它們非常相似,但內(nèi)部處理實(shí)際上卻各不相同。換句話說,各對(duì)象雖然內(nèi)部處理不同,但對(duì)于使用者(調(diào)用者)來講,它們卻是相同的。

4.1 學(xué)生的“自我介紹”

在前面提到的學(xué)生類,包含姓名、學(xué)號(hào)、性別、身高、體重等屬性,并對(duì)外提供了一個(gè)“自我介紹”方法。

//微信公眾號(hào):嵌入式系統(tǒng)
void?student_self_introduction(struct?student?*p_this)
{
????printf("Hi!?My?name?is?%s,?I'm?a?%s.?My?school?number?is?%d.?My?height?is?%fcm?and?weight?is?%fkg",
???????????p_this->name,
???????????(p_this->sex?==?'M')???"boy"?:?"girl",
???????????p_this->id,
???????????p_this->height,
???????????p_this->weight);
}

假設(shè)一個(gè)場(chǎng)景,開學(xué)第一課所有同學(xué)依次作一個(gè)簡(jiǎn)單的自我介紹,調(diào)用所有同學(xué)的自我介紹方法即可,范例程序如下:

void?first_class(struct?student?*p_students,?int?num)
{
????int?i;
????for(i?=?0;?i?<?num;?i++)
????{
????????student_self_introduction(&p_students[i]);
????}
}

調(diào)用該函數(shù)前,需要將所有學(xué)生對(duì)象創(chuàng)建好,并存于一個(gè)數(shù)組中,假定一個(gè)班級(jí)有 50個(gè)學(xué)生,則調(diào)用示意代碼如下:

int?main()
{
????struct?student?student[50];

????/*根據(jù)每個(gè)學(xué)生的信息,依次創(chuàng)建各個(gè)學(xué)生對(duì)象*/
????student_init(&student[0],?"zhangsan",?2024001,?'M',?173,?60);
????student_init(&student[1],?"lisi",?2024002,?'F',?168,?65);
?//?...

????/*上第一節(jié)課????*/
????first_class(student,?50);
}

上面的實(shí)現(xiàn)代碼,假定了學(xué)生的“自我介紹”格式是完全相同的,都是將個(gè)人信息陳述一遍,顯然,這樣的自我介紹無法體現(xiàn)每個(gè)學(xué)生的個(gè)性和差異。例如,一個(gè)名叫張三的學(xué)生,其期望這樣介紹自己:

“親愛的老師,同學(xué)們!我叫張三,來自湖北仙桃,是一個(gè)自信開朗,積極向上的人,我有著廣泛的興趣愛好,喜歡打籃球、看書、下棋、聽音樂……”

每個(gè)學(xué)生自我介紹的內(nèi)容并不期望千篇一律。若不基于多態(tài)的思想,最簡(jiǎn)單粗暴的方式是每個(gè)學(xué)生都提供一個(gè)自我介紹方法,例如 student_zhangsan_introduction()。這種情況下每個(gè)學(xué)生提供的方法都不相同(函數(shù)名不同),根本無法統(tǒng)一調(diào)用,此時(shí),第一節(jié)課的調(diào)用將會(huì)大改,需要依次調(diào)用每個(gè)學(xué)生提供的不同的自我介紹方法,例如:

void?first_class()
{
????student_zhangsan_introduction(&zhangshan);?//?張三自我介紹
????student_lisi_introduction(&lisi);???//?李四自我介紹
?//?….
}

無法使用同樣的調(diào)用形式(函數(shù))完成不同對(duì)象的“自我介紹”。對(duì)于調(diào)用者來講,需要關(guān)注每個(gè)對(duì)象提供的特殊方法,復(fù)雜度將提升。

使用多態(tài)的思想即可很好的解決這個(gè)問題,進(jìn)而保證 firstt_class()的內(nèi)容不變,雖然每個(gè)對(duì)象方法的實(shí)現(xiàn)不同,但可以使用同樣的形式調(diào)用它。在 C 語言中,函數(shù)指針就是解決這個(gè)問題的“利器”。

函數(shù)指針的原型決定了調(diào)用方法,例如定義函數(shù)指針:

int?(*student_self_introduction)?(struct?student?*p_student);

無論該函數(shù)指針指向何處,都表示該函數(shù)指針指向的是 int 類型返回值,具有一個(gè)*p_student 參數(shù)的函數(shù),其調(diào)用形式如下:

student_self_introduction(p_student);

函數(shù)指針的指向代表了函數(shù)的實(shí)現(xiàn),指向不同的函數(shù)就代表了不同的實(shí)現(xiàn)?;诖?,為了使每個(gè)學(xué)生對(duì)象可以有自己獨(dú)特的介紹方式,在學(xué)生類的定義中,可以不實(shí)現(xiàn)自我介紹方法,但可以通過函數(shù)指針約定自我介紹方法的調(diào)用形式。更新學(xué)生類的定義:

student.h?文件```

```c
//微信公眾號(hào):嵌入式系統(tǒng)
#ifndef?__STUDENT_H
#define?__STUDENT_H

struct?student
{
????int?(*student_self_introduction)(struct?student?*p_student);??/*?新增個(gè)性化自我介紹?*/
????char?name[10];??/*?姓名?(假定最長(zhǎng)?10?字符)*/
????unsigned?int?id;?/*?學(xué)號(hào)?*/
????char?sex;???/*?性別:'M',男;'F'?,女?*/
????float?height;??/*?身高?*/
????float?weight;??/*?體重?*/
};

int?student_init(struct?student?*p_student,
?????????????????char?*p_name,
?????????????????unsigned?int?id,
?????????????????char?sex,
?????????????????float?height,
?????????????????float?weight,
?????????????????int?(*student_self_introduction)(struct?student?*));

/*?學(xué)生類提供的自我介紹方法?*/
static?inline?int?student_self_introduction(struct?student?*p_student)
{
????return?p_student->student_self_introduction(p_student);
}

#endif

此時(shí),對(duì)于外界來講,學(xué)生類“自我介紹方法”的調(diào)用形式并未發(fā)生任何改變,函數(shù)原型還是一樣的(由于只有一行代碼,因而以內(nèi)聯(lián)函數(shù)的形式存放到了頭文件中)。基于此,“第一節(jié)課的內(nèi)容”可以保持完全不變(for循環(huán)調(diào)用全部)。在這種方式下,每個(gè)對(duì)象在初始化時(shí),需要指定自己特殊的自我介紹方,例如張三對(duì)象的創(chuàng)建過程為:

int?student_zhangsan_introduction(struct?student?*p_student)
{
????const?char?*str?=?"親愛的老師,同學(xué)們!我叫張三,來自湖北仙桃,是一個(gè)自信開朗,積極向上的人,我有著廣泛的興趣愛好,喜歡打籃球、看書、下棋、聽音樂……";

????printf("%sn",?str);
????return?0;
}

int?main()
{
????struct?student?student[50];

????/*?根據(jù)每個(gè)學(xué)生的信息,依次創(chuàng)建各個(gè)學(xué)生對(duì)象?*/
????student_init(&student[0],?"zhangsan",?2024001,?'M',?173,?60,?student_zhangsan_introduction);

????//?...

????/*?上第一節(jié)課?*/
????first_class(student,?50);
}

多態(tài)的核心是:對(duì)于上層調(diào)用者,不同的對(duì)象可以使用完全相同的操作方法,但是每個(gè)對(duì)象可以有各自不同的實(shí)現(xiàn)方式。多態(tài)是面向?qū)ο缶幊谭浅V匾奶匦?,C 語言依賴指針實(shí)現(xiàn)多態(tài)。

(微信公眾號(hào)【嵌入式系統(tǒng)】很多設(shè)計(jì)模式或硬件多型號(hào)適配都是基于這個(gè)基礎(chǔ),可以參考《嵌入式軟件的設(shè)計(jì)模式(上)》)。

4.2 I/O 設(shè)備驅(qū)動(dòng)

C 程序使用 printf()打印日志信息,在 PC 上運(yùn)行時(shí),日志信息可能輸出到控制臺(tái),而在嵌入式系統(tǒng)中,信息可能通過某個(gè)串口輸出。printf()函數(shù)的解釋是輸出信息至 STDOUT(標(biāo)準(zhǔn)輸出)。顯然printf()函數(shù)就具有多態(tài)性,對(duì)于用戶來講,其調(diào)用形式是確定的,但內(nèi)部具體輸出信息到哪里,卻會(huì)隨著 STDOUT 的不同而不同。

在一些操作系統(tǒng)中(如Linux),硬件設(shè)備(例串口、ADC 等)的操作方法都和文件操作方法類似(一切皆文件),都可以通過 open()、close()、read()、write()等幾個(gè)標(biāo)準(zhǔn)函數(shù)進(jìn)行操作。為統(tǒng)一 I/O 設(shè)備的使用方法,要求每個(gè) I/O 設(shè)備都提供 open、close、read、write 這幾個(gè)標(biāo)準(zhǔn)函數(shù)的實(shí)現(xiàn),即每個(gè) I/O設(shè)備的驅(qū)動(dòng)程序,對(duì)這些標(biāo)準(zhǔn)函數(shù)的實(shí)現(xiàn)在函數(shù)調(diào)用上必須保持一致。這本質(zhì)上就是一個(gè)多態(tài)問題,即以同樣的方法使用不同的 I/O 設(shè)備。

通過函數(shù)指針解決這個(gè)問題,首先定義file_ops結(jié)構(gòu)體,包含了相對(duì)應(yīng)的函數(shù)指針,指向I/O 設(shè)備針對(duì)操作的實(shí)現(xiàn)函數(shù)。

file_ops.h?文件
//微信公眾號(hào):嵌入式系統(tǒng)
//代碼片段只是原理性展示
struct?file_ops
{
????void?(*open)(char?*name,?int?mode);
????void?(*close)();
????int?(*read)();
????void?(*write)();
};

對(duì)于 I/O設(shè)備,其驅(qū)動(dòng)程序提供這 4個(gè)函數(shù)的實(shí)現(xiàn),并將 file_ops結(jié)構(gòu)體的函數(shù)指針指向?qū)?yīng)的函數(shù)。

#include?"file_ops.h"

static?void?open(char?*name,?int?mode)
{
????//...
}

static?void?close()
{
????//...
}

static?int?read()
{
????//...
}

static?void?write()
{
????//...
}

struct?file_ops?my_console?=?{open,?close,?read,?write};

所有的函數(shù)都使用 static修飾符,避免與外部的函數(shù)產(chǎn)生命名沖突。對(duì)于該設(shè)備,僅對(duì)外提供了一個(gè)可以使用的 file_ops 對(duì)象 my_console。

上面展示了設(shè)備 I/O 的一般管理方法,其中的編程方法或技巧正是面向?qū)ο缶幊讨卸鄳B(tài)的基礎(chǔ),也再一次展現(xiàn)了函數(shù)指針在多態(tài)中的重要地位,多態(tài)可以視為函數(shù)指針的一種典型應(yīng)用。(微信公眾號(hào)【嵌入式系統(tǒng)】類似使用是Linux設(shè)備驅(qū)動(dòng)的基礎(chǔ))。

4.3 帶檢查功能的棧

前面范例實(shí)現(xiàn)了棧的核心邏輯(入棧和出棧),假設(shè)現(xiàn)在增加需求,實(shí)現(xiàn)“帶檢查功能的棧”,即在數(shù)據(jù)入棧之前,必須進(jìn)行特定的檢查,“檢查通過”后才能壓人棧中。檢查方式有多種:

范圍檢查:必須在特定的范圍之內(nèi),比如1 ~ 9,才視為檢查通過;奇偶檢查:必須是奇數(shù)或者偶數(shù),才視為檢查通過;變化檢查:值必須增加(比上一次的值大),才視為檢查通過。

4.3.1 基于繼承實(shí)現(xiàn)“帶范圍檢查功能”的棧

先不考慮多種檢查方式,僅實(shí)現(xiàn)范圍檢查。參照“命名棧”的實(shí)現(xiàn),使用繼承方式,在普通棧的基礎(chǔ)上實(shí)現(xiàn)一個(gè)新類,范例程序如下:

stack_with_range_check.h??帶范圍檢查的棧
#ifndef?__STACK_WITH_RANGE_CHECK_H
#define?__STACK_WITH_RANGE_CHECK_H

#include?"stack.h"??/*?包含基類頭文件?*/

struct?stack_with_range_check
{
????struct?stack?super;??/*?基類(超類)*/
????int?min;????/*?最小值?*/
????int?max;????/*?最大值?*/
};

int?stack_with_range_check_init(struct?stack_with_range_check?*p_stack,
????????????????????????????????int?*p_buf,
????????????????????????????????int??size,
????????????????????????????????int?min,?int?max);

/*?入棧?*/
int?stack_with_range_check_push(struct?stack_with_range_check?*p_stack,?int?val);

/*?出棧?*/
int?stack_with_range_check_pop(struct?stack_with_range_check?*p_stack,?int?*p_val);

#endif

帶范圍檢查的棧 C 文件 stack_with_range_check.c

//微信公眾號(hào):嵌入式系統(tǒng)
#include?"stack_with_range_check.h"

int?stack_with_range_check_init(struct?stack_with_range_check?*p_stack,
????????????????????????????????int?*p_buf,
????????????????????????????????int?size,
????????????????????????????????int?min,?int?max)
{
????/*?初始化基類?*/
????stack_init(&p_stack->super,?p_buf,?size);

????/*?初始化子類成員?*/
????p_stack->min?=?min;
????p_stack->max?=?max;
????return?0;
}

int?stack_with_range_check_push(struct?stack_with_range_check?*p_stack,?int?val)
{
????if((val?>=?p_stack->min)?&&?(val?<=?p_stack->max))??//差異點(diǎn)
????{
????????return?stack_push(&p_stack->super,?val);
????}
????return?-1;
}

int?stack_with_range_check_pop(struct?stack_with_range_check?*p_stack,?int?*p_val)
{
????return?stack_pop(&p_stack->super,?p_val);
}

為了接口的簡(jiǎn)潔性,沒有再展示解初始化等函數(shù)的定義。新增入棧時(shí)作檢查,出棧和普通棧是完全相同的,但基于最小知識(shí)原則也封裝了一個(gè) pop 接口,使該類的用戶完全不需要關(guān)心普通棧。

依照這個(gè)方法,可以實(shí)現(xiàn)其它檢查方式的棧。核心是實(shí)現(xiàn)帶檢查功能的入棧函數(shù),因而僅簡(jiǎn)單展示另外兩種檢查方式下入棧函數(shù)的實(shí)現(xiàn),分別如下:

//奇偶檢查入棧函數(shù)
int?stack_with_oddeven_check_push(struct?stack_with_oddeven_check?*p_stack,?int?val)
{
????if(((p_stack->iseven)?&&?((val?%?2)?==?0))?||?((!p_stack->iseven)?&&?((val?%?2)?!=?0)))
????{
????????return?stack_push(&p_stack->super,?val);?//檢查通過:偶校驗(yàn)且為偶數(shù),或奇校驗(yàn)且為奇數(shù)
????}
????return?-1;
}

//變化檢查入棧函數(shù)
int?stack_with_change_check_push(struct?stack_with_change_check?*p_stack,?int?val)
{
????if(p_stack->pre_value?<?val)
????{
????????p_stack->pre_value?=?val;
????????return?stack_push(&p_stack->super,?val);?//檢查通過:本次入棧值大于上一次的值
????}
????return?-1;
}

由此可見,這種實(shí)現(xiàn)方式存在一定的缺陷,不同檢查方法對(duì)應(yīng)的入棧函數(shù)不相同,對(duì)于用戶來講,使用不同的檢查功能,就必須調(diào)用不同的入棧函數(shù)。即操作不同的棧使用不同的接口。但觀察幾個(gè)入棧函數(shù),其入棧方法類似,示意代碼如下:

int?stack_XXX_push(struct?stack_XXX?*p_stack,?int?val)
{
????if(檢查通過)??//不同棧的差異僅是檢測(cè)條件不同
????{
????????return?stack_push(&p_stack->super,?val);
????}
????return?-1;
}

可使用多態(tài)思想,將“檢查”函數(shù)的調(diào)用形式標(biāo)準(zhǔn)化編寫一個(gè)通用的、與具體檢查方式無關(guān)的入棧函數(shù)。

4.3.2 基于多態(tài)實(shí)現(xiàn)通用的“帶檢查功能的棧”

使用函數(shù)指針表示“檢查功能”,指向不同的檢查函數(shù)??梢远x一個(gè)包含函數(shù)指針的類:

struct?stack_with_validate
{
????struct?stack?super;????????????/*?基類(超類)*/
????int?(*validate)(struct?stack_with_validate?*p_this,?int?val);??/*?檢查函數(shù)?*/
};

和其它普通方法一樣,類中抽象方法(函數(shù)指針)的第一個(gè)成員同樣是指向該類對(duì)象的指針。此時(shí),數(shù)據(jù)入棧前的檢查工作交給 validate 指針?biāo)赶虻暮瘮?shù)實(shí)現(xiàn)。假定其指向的函數(shù)在檢查數(shù)據(jù)時(shí),返回 0 表示檢查通過可入棧,其它值表示檢查未通過。完整的帶檢查功能的棧實(shí)現(xiàn)范例如下:

帶檢查功能的棧 H 文件(stack_with_validate.h)

//微信公眾號(hào):嵌入式系統(tǒng)

#ifndef?__STACK_WITH_VALIDATE_H
#define?__STACK_WITH_VALIDATE_H

#include?"stack.h"?????/*?包含基類頭文件?*/
struct?stack_with_validate
{
????struct?stack??super;???/*?基類(超類)*/
????int?(*validate)(struct?stack_with_validate?*p_this,?int?val);?/*?檢查函數(shù)?*/
};


int?stack_with_validate_init(struct?stack_with_validate?*p_stack,
?????????????????????????????int?*p_buf,
?????????????????????????????int?size,
?????????????????????????????int?(*validate)(struct?stack_with_validate?*,?int));

/*?入棧?*/
int?stack_with_validate_push(struct?stack_with_validate?*p_stack,?int?val);

/*?出棧?*/
int?stack_with_validate_pop(struct?stack_with_validate?*p_stack,?int?*p_val);

#endif

帶檢查功能的棧 C 文件(stack_with_validate.c)

#include?"stack_with_validate.h"
#include?"stdio.h"

int?stack_with_validate_init(struct?stack_with_validate?*p_stack,
?????????????????????????????int?*p_buf,
?????????????????????????????int?size,
?????????????????????????????int?(*validate)(struct?stack_with_validate?*,?int))

{
????/*?初始化基類?*/
????stack_init(&p_stack->super,?p_buf,?size);
????p_stack->validate?=?validate;??//檢查條件,上層說了算
????return?0;
}

int?stack_with_validate_push(struct?stack_with_validate?*p_stack,?int?val)
{
????if(?(p_stack->validate?==?NULL)?||?
????????((p_stack->validate?!=?NULL)?&&?(p_stack->validate(p_stack,?val)?==?0))?)
????{
????????return?stack_push(&p_stack->super,?val);
????}
????return?-1;
}

int?stack_with_validate_pop(struct?stack_with_validate?*p_stack,?int?*p_val)
{
????return?stack_pop(&p_stack->super,?p_val);
}

帶某種檢查功能的棧,重點(diǎn)是實(shí)現(xiàn)其中的 validate 方法?;趲z查的棧,實(shí)現(xiàn)帶范圍檢查的棧,程序詳見如下:

帶范圍檢查的棧 H 文件更新(stack_with_range_check.h)

#ifndef?__STACK_WITH_RANGE_CHECK_H
#define?__STACK_WITH_RANGE_CHECK_H

#include?"stack_with_validate.h"??/*?包含基類頭文件?*/

struct?stack_with_range_check
{
????struct?stack_with_validate?super;??/*?基類(超類)*/
????int?min;????????/*?最小值?*/
????int?max;????????/*?最大值?*/
};

struct?stack_with_validate?*?stack_with_range_check_init(struct?stack_with_range_check?*p_stack,
????????int?*p_buf,
????????int?size,
????????int?min,
????????int?max);

#endif

帶范圍檢查的棧 C 文件更新(stack_with_range_check.c)

#include?"stack_with_range_check.h"

static?int?_validate(struct?stack_with_validate?*p_this,?int?val)
{
????struct?stack_with_range_check?*p_stack?=?(struct?stack_with_range_check?*)p_this;
?
????if((val?>=?p_stack->min)?&&?(val?<=?p_stack->max))
????{
????????return?0;?/*?檢查通過?*/
????}

????return?-1;
}

struct?stack_with_validate?*?stack_with_range_check_init(struct?stack_with_range_check?*p_stack,
????????int?*p_buf,
????????int?size,
????????int?min,
????????int?max)
{
????/*?初始化基類?*/
????stack_with_validate_init(&p_stack->super,?p_buf,?size,?_validate);
?
????/*?初始化子類成員?*/
????p_stack->min?=?min;
????p_stack->max?=?max;
????return?0;
}

帶范圍檢查的棧,主要目的就是實(shí)現(xiàn)“檢查功能”對(duì)應(yīng)的函數(shù):_validate,并將其作為 validate 函數(shù)指針(抽象方法)的值。

在面向?qū)ο缶幊讨?,包含抽象方法的類通常稱之為抽象類,抽象類不能直接實(shí)例化(因?yàn)槠溥€有方法未實(shí)現(xiàn)),抽象類只能被繼承,且由子類實(shí)現(xiàn)其中定義的抽象方法。在 UML 類圖中,抽象類的類名和其中的抽象方法均使用斜體表示,普通棧、帶檢查功能的棧和帶范圍檢查的棧,它們之間的關(guān)系詳見圖。

帶范圍檢查的棧,其主要作用是實(shí)現(xiàn)其父類中定義的抽象方法,進(jìn)而創(chuàng)建一個(gè)真正的“帶檢查功能”的棧對(duì)象(此時(shí)的抽象方法已實(shí)現(xiàn)),該對(duì)象即可提交給外部使用。帶范圍檢查的棧并沒有其他特殊的方法,因而在其初始化完成后,通過初始化函數(shù)的返回值向外界提供了一個(gè)“帶檢查功能”的棧對(duì)象,后續(xù)用戶即可使用 stack_with_validate.h 文件中的push 和 pop 方法操作該對(duì)象。

帶范圍檢查的棧使用范例如下:

//微信公眾號(hào):嵌入式系統(tǒng)
#include?"stack_with_range_check.h"
#include?"stdio.h"

int?main()
{
????int??val;
????int??buf[20];
????int??i;
????int??test_data[5]?=?{2,?4,?5,?3,?10};

????struct?stack_with_range_check??stack;

????struct?stack_with_validate?*p_stack?=?stack_with_range_check_init(&stack,?buf,?20,?1,?9);

????for(i?=?0;?i?<?5;?i++)
????{
????????if(stack_with_validate_push(p_stack,?test_data[i])?!=?0)
????????{
????????????printf("The?data?%d?push?failed!n",?test_data[i]);
????????}
????}

????printf("The?pop?data:?");
????while(1)??/*?彈出所有數(shù)據(jù)?*/
????{
????????if(stack_with_validate_pop(p_stack,?&val)?==?0)
????????{
????????????printf("%d?",?val);
????????}
????????else
????????{
????????????break;
????????}
????}
????return?0;
}

無論何種檢查方式,其主要目的都是創(chuàng)建“帶檢查功能”的棧對(duì)象(完成抽象方法的實(shí)現(xiàn))。創(chuàng)建完畢后,對(duì)于用戶操作方法都是完全相同的 stack_with_validate_push 和 stack_with_validate_pop ,與檢查方式無關(guān)。為避免贅述,這里不再實(shí)現(xiàn)另外兩種檢查功能的棧,僅展示出他們的類圖。

在這里插入圖片描述

在一些大型項(xiàng)目中,初始化過程往往和應(yīng)用程序是分離的(即stack_with_range_check_init 內(nèi)部封閉不可見),也就是說,對(duì)于用戶來講,其僅會(huì)獲取到一個(gè) struct stack_with_validate *類型的指針,其指向某個(gè)“帶檢查功能的?!?,實(shí)際檢查什么,用戶可能并不關(guān)心,應(yīng)用程序基于該類型指針編程,將使應(yīng)用程序與具體檢查功能無關(guān),即使后續(xù)更換為其它檢查方式,應(yīng)用程序也不需要做任何改動(dòng)。

4.4 抽象分離

如果是硬件資源有限,功能單一或大概率無需擴(kuò)展的嵌入式軟件開發(fā),進(jìn)行到這基本可以滿足需求;如果是復(fù)雜應(yīng)用,且硬件資源充足還可繼續(xù)優(yōu)化。

4.4.1 檢查功能抽象

前面的實(shí)現(xiàn)中,將檢查功能視為棧的一種擴(kuò)展(使用繼承),檢查邏輯直接在相應(yīng)的擴(kuò)展類中實(shí)現(xiàn)。這就使檢查功能與棧綁定在一起,檢查功能的實(shí)現(xiàn)無法獨(dú)立復(fù)用。如果要實(shí)現(xiàn)一個(gè)“帶檢查功能的隊(duì)列”,同樣是上述的 3 種檢查邏輯,期望能夠復(fù)用檢查邏輯相關(guān)的代碼。顯然,由于當(dāng)前檢查邏輯的實(shí)現(xiàn)與棧捆綁在一起,無法單獨(dú)提取出來復(fù)用。

檢查功能與棧的綁定,主要在“帶檢查功能的?!敝畜w現(xiàn),該類的定義如下:

struct?stack_with_validate
{
????struct?stack??super;???/*?基類(超類)*/
????int?(*validate)(struct?stack_with_validate?*p_this,?int?val);?/*?檢查函數(shù)?*/
};

super 用于繼承自普通棧,validate 表示一個(gè)抽象的數(shù)據(jù)檢查方法,不同的檢查方法,通過該指針?biāo)赶虻暮瘮?shù)體現(xiàn)。由于檢查方法validate是該類的一個(gè)方法,檢查邏輯與棧綁定。為了解綁分離,可以將檢查邏輯放到獨(dú)立的與棧無關(guān)的類中,額外定義一個(gè)抽象的校驗(yàn)器類,專門表示數(shù)據(jù)檢查邏輯:

struct?validator
{
????int?(*validate)(struct?validator?*p_this,?int?val);?/*?檢查函數(shù)?*/
};

雖然該類僅包含 validate 函數(shù)指針,但需注意該函數(shù)指針類型的變化,其第一個(gè)參數(shù)為指向校驗(yàn)器的指針,而在“帶檢查功能的?!敝?,其第一個(gè)參數(shù)是指向“帶檢查功能的?!钡闹羔?。通過該類的定義,明確的將檢查邏輯封裝到獨(dú)立的校驗(yàn)器類中,與棧再無任何關(guān)聯(lián)。不同的檢查邏輯,可以在其子類中實(shí)現(xiàn),校驗(yàn)器類和各個(gè)子類之間的關(guān)系如下:由于校驗(yàn)器類僅包含一個(gè)函數(shù)指針,因此其只需要在頭文件中定義出類即可,程序如下:

校驗(yàn)器類定義(validator.h)

#ifndef?__VALIDATOR_H
#define?__VALIDATOR_H

struct?validator
{
????int?(*validate)(struct?validator?*p_this,?int?val);
};

static?inline?int?validator_init(struct?validator?*p_validator,
?????????????????????????????????int?(*validate)(struct?validator?*,?int))
{
????p_validator->validate?=?validate;
????return?0;
}

static?inline?int?validator_validate(struct?validator?*p_validator,?int?val)??/*?校驗(yàn)函數(shù)?*/
{
????if(p_validator->validate?==?NULL)??/*?校驗(yàn)函數(shù)為空,視為無需校驗(yàn)?*/
????{
????????return?0;
????}
????return?p_validator->validate(p_validator,?val);
}

#endif

初始化函數(shù)負(fù)責(zé)為 validate 賦值,validator_validate 函數(shù)是校驗(yàn)器對(duì)外提供的校驗(yàn)函數(shù),在其實(shí)現(xiàn)中僅調(diào)用了 validate 函數(shù)指針指向的函數(shù)。由于函數(shù)都比較簡(jiǎn)單,因而直接使用了內(nèi)聯(lián)函數(shù)的形式進(jìn)行了定義。接下來以范圍校驗(yàn)為例,實(shí)現(xiàn)一個(gè)范圍校驗(yàn)器。

范圍校驗(yàn)器 H 文件內(nèi)容(validator_range_check.h)

#ifndef?__VALIDATOR_RANGE_CHECK_H
#define?__VALIDATOR_RANGE_CHECK_H

#include?"validator.h"
struct?validator_range_check
{
????struct?validator?super;
????int?min;
????int?max;
};

struct?validator*?validator_range_check_init(struct?validator_range_check?*p_validator,?int?min,?int?max);

#endif

范圍校驗(yàn)器 C 文件內(nèi)容(validator_range_check.c)

//微信公眾號(hào):嵌入式系統(tǒng)
#include?"validator_range_check.h"

static?int?_validate(struct?validator?*p_this,?int?val)
{
????struct?validator_range_check?*p_stack?=?(struct?validator_range_check?*)p_this;
????if((val?>=?p_stack->min)?&&?(val?<=?p_stack->max))
????{
????????return?0;??/*?檢查通過?*/
????}
????return?-1;
}

struct?validator*?validator_range_check_init(struct?validator_range_check?*p_validator,?int?min,?int?max)
{
????validator_init(&p_validator->super,?_validate);
????p_validator->min?=?min;
????p_validator->max?=?max;
????return?&p_validator->super;
}

由于 validator_range_check 類僅用于實(shí)現(xiàn) validator 抽象類中定義的抽象方法,其初始化函數(shù)可以直接對(duì)外返回一個(gè)標(biāo)準(zhǔn)的校驗(yàn)器(其中的抽象方法已實(shí)現(xiàn))。按照同樣的方法,可以實(shí)現(xiàn)validator_oddeven_check 類和 validator_change_check 類。將檢查功能從“帶檢查功能的?!敝蟹蛛x出來之后,“帶檢查功能的?!敝芯蜔o需再維護(hù)檢查功能對(duì)應(yīng)的抽象方法。其可以通過依賴的方式使用檢查功能,即依賴一個(gè)校驗(yàn)器。在類圖中,依賴關(guān)系可以使用一個(gè)虛線箭頭表示,箭頭指向被依賴的類,示意圖如下:“帶檢查功能的?!鳖惗x如下:

struct?stack_with_validate
{
????struct?stack?super;????/*?基類(超類)*/
????struct?validator?*p_validator;?/*?依賴的校驗(yàn)器?*/
};

與先前相比,其核心變化是由一個(gè) validate 函數(shù)指針(指向具體的檢查方法)變更為 p_validator 指針(指向抽象的檢查方法),變化雖小,但是兩種截然不同的設(shè)計(jì)理念。之前的方式是定義了一個(gè)抽象方法,而現(xiàn)在的方式是依賴于一個(gè)校驗(yàn)器對(duì)象。

基于此更新“帶檢查功能的棧”類的實(shí)現(xiàn)如下:

帶檢查功能的棧 H 文件更新(stack_with_validate.h)

#ifndef?__STACK_WITH_VALIDATE_H
#define?__STACK_WITH_VALIDATE_H

#include?"stack.h"???/*?包含基類頭文件?*/
#include?"validator.h"

struct?stack_with_validate
{
????struct?stack?super;??/*?基類(超類)*/
????struct?validator?*p_validator;
};

int?stack_with_validate_init(struct?stack_with_validate?*p_stack,
?????????????????????????????int?*p_buf,
?????????????????????????????int?size,
?????????????????????????????struct?validator?*p_validator);

int?stack_with_validate_push(struct?stack_with_validate?*p_stack,?int?val);
int?stack_with_validate_pop(struct?stack_with_validate?*p_stack,?int?*p_val);

#endif

帶檢查功能的棧 C 文件更新(stack_with_validate.c)

//微信公眾號(hào):嵌入式系統(tǒng)
#include?"stack_with_validate.h"
#include?"stdio.h"

int?stack_with_validate_init(struct?stack_with_validate?*p_stack,
?????????????????????????????int?*p_buf,
?????????????????????????????int?size,
?????????????????????????????struct?validator?*p_validator)
{
????stack_init(&p_stack->super,?p_buf,?size);
????p_stack->p_validator?=?p_validator;
????return?0;
}

int?stack_with_validate_push(struct?stack_with_validate?*p_stack,?int?val)
{
????if((p_stack->p_validator?==?NULL)?||?(validator_validate(p_stack->p_validator,?val)?==?0))?//注意差別
????{
????????return?stack_push(&p_stack->super,?val);
????}
????return?-1;
}

int?stack_with_validate_pop(struct?stack_with_validate?*p_stack,?int?*p_val)
{
????return?stack_pop(&p_stack->super,?p_val);
}

“帶檢查功能的?!钡膽?yīng)用接口(push 和 pop)并沒有發(fā)生任何改變,應(yīng)用程序可以被復(fù)用,測(cè)試更新后的帶檢查功能的棧:

#include?"stack_with_validate.h"
#include?"validator_range_check.h"
#include?"stdio.h"
int?main()
{
????int?buf[20];
????struct?stack_with_validate?stack;
????struct?validator_range_check?validator_range_check;

????/*?獲取范圍檢查校驗(yàn)器?*/
????struct?validator?*p_validator?=?validator_range_check_init(&validator_range_check,?1,?9);
????stack_with_validate_init(&stack,?buf,?20,?p_validator);
?
????stack_validate_application(&stack);//使用和先前繼承方式一樣,實(shí)現(xiàn)忽略
?
????return?0;
}

4.4.2 ?定義抽象棧

定義校驗(yàn)器類后,整個(gè)系統(tǒng)實(shí)現(xiàn)了兩種棧:普通棧和“帶檢查功能的?!?,無論什么棧,對(duì)于用戶來講都是實(shí)現(xiàn)入棧和出棧兩個(gè)核心邏輯。兩種棧提供兩種入棧和出棧方法。

普通棧提供的方法為:

int?stack_push(struct?stack?*p_stack,?int?val);??/*?入棧?*/
int?stack_pop(struct?stack?*p_stack,?int?*p_val);?/*?出棧?*/

“帶檢查功能的棧”提供的方法為:

int?stack_with_validate_push(struct?stack_with_validate?*p_stack,?int?val);??/*?入棧?*/
int?stack_with_validate_pop(struct?stack_with_validate?*p_stack,?int?*p_val);?/*?出棧?*/

用戶執(zhí)行入棧和出棧操作,使用不同類的棧,調(diào)用的函數(shù)不同。通過多態(tài)思想,將入棧和出棧定義為抽象方法(函數(shù)指針),則可以達(dá)到這樣的效果:無論使用何種棧,都可以使用相同的方法來實(shí)現(xiàn)入棧和出棧?;诖硕x抽象棧。

抽象棧類定義(stack.h)

#ifndef?__STACK_H
#define?__STACK_H

struct?stack
{
????int?(*push)(struct?stack?*p_stack,?int?val);
????int?(*pop)(struct?stack?*p_stack,?int?*p_val);
};

static?inline?int?stack_init(struct?stack?*p_stack,
?????????????????????????????int?(*push)(struct?stack?*,?int),
?????????????????????????????int?(*pop)(struct?stack?*,?int?*))
{
????p_stack->push?=?push;
????p_stack->pop?=?pop;
}

static?inline?int?stack_push(struct?stack?*p_stack,?int?val)
{
????return?p_stack->push(p_stack,?val);
}

static?inline?int?stack_pop(struct?stack?*p_stack,?int?*p_val)
{
????return?p_stack->pop(p_stack,?p_val);
}

#endif

基于抽象棧的定義,使用抽象棧提供的接口實(shí)現(xiàn)一個(gè)通用的應(yīng)用程序,該應(yīng)用程序與底層細(xì)節(jié)無關(guān),任何棧都可以使用該應(yīng)用程序進(jìn)行測(cè)試。

基于抽象棧實(shí)現(xiàn)的應(yīng)用程序:

#include?"stack.h"
#include?"stdio.h"
int?stack_application(struct?stack?*p_stack)
{
????int?i;
????int?val;
????int?test_data[5]?=?{2,?4,?5,?3,?10};

????for(i?=?0;?i?<?5;?i++)
????{
????????if(stack_push(p_stack,?test_data[i])?!=?0)
????????{
????????????printf("The?data?%d?push?failed!n",?test_data[i]);
????????}
????}

????printf("The?pop?data:?");
????while(1)?
????{
????????if(stack_pop(p_stack,?&val)?==?0)
????????{
????????????printf("%d",?val);
????????}
????????else
????????{
????????????break;
????????}
????}
????return?0;
}

先有應(yīng)用層代碼再有底層代碼。在實(shí)現(xiàn)具體棧之前,就可以開始編寫應(yīng)用程序(微信公眾號(hào)【嵌入式系統(tǒng)】這就是依賴倒置原則,可參考《嵌入式軟件設(shè)計(jì)原則隨想》)。實(shí)現(xiàn)普通棧:

普通棧 H 文件內(nèi)容(stack_normal.h)

#ifndef?__STACK_NORMAL_H
#define?__STACK_NORMAL_H

#include?"stack.h"
struct?stack_normal
{
????struct?stack?super;
????int?top;???/*?棧頂?*/
????int?*p_buf;???/*?棧緩存?*/
????unsigned?int?size;?/*?棧緩存的大小?*/
};

struct?stack?*?stack_normal_init(struct?stack_normal?*p_stack,?int?*p_buf,?int?size);

#endif

普通棧 C 文件內(nèi)容(stack_normal.c)

//微信公眾號(hào):嵌入式系統(tǒng)
#include?"stack_normal.h"

static?int?_push(struct?stack?*p_this,?int?val)
{
????struct?stack_normal?*p_stack?=?(struct?stack_normal?*)p_this;
????if(p_stack->top?!=?p_stack->size)
????{
????????p_stack->p_buf[p_stack->top++]?=?val;
????????return?0;
????}
????return?-1;
}

static?int?_pop(struct?stack?*p_this,?int?*p_val)
{
????struct?stack_normal?*p_stack?=?(struct?stack_normal?*)p_this;
????if(p_stack->top?!=?0)
????{
????????*p_val?=?p_stack->p_buf[--p_stack->top];
????????return?0;
????}
????return?-1;
}

struct?stack?*?stack_normal_init(struct?stack_normal?*p_stack,?int?*p_buf,?int?size)
{
????p_stack->top?=?0;
????p_stack->size?=?size;

????p_stack->p_buf?=?p_buf;
????stack_init(&p_stack->super,?_push,?_pop);
????return?&p_stack->super;
}

基于普通類的實(shí)現(xiàn),測(cè)試普通棧類:

#include?"stack_normal.h"
int?main()
{
????int?buf[20];

????struct?stack_normal?stack;
????struct?stack?*p_stack?=?stack_normal_init(&stack,?buf,?20);
????stack_application(p_stack);
????return?0;
}

“帶檢查功能的?!笔窃谄胀5幕A(chǔ)上,增加了檢查功能,實(shí)現(xiàn)范例程序如下:

帶檢查功能的棧 H 文件更新(stack_with_validate.h)

#ifndef?__STACK_WITH_VALIDATE_H
#define?__STACK_WITH_VALIDATE_H

#include?"stack.h"???/*?包含基類頭文件?*/
#include?"validator.h"


struct?stack_with_validate
{

????struct?stack?super;????/*?基類(超類)*/
????struct?stack*p_normal_stack;??/*?依賴于普通棧的實(shí)現(xiàn)?*/
????struct?validator?*p_validator;
};

struct?stack?*?stack_with_validate_init(struct?stack_with_validate?*p_stack,
????????????????????????????????????????struct?stack??*p_normal_stack,
????????????????????????????????????????struct?validator?*p_validator);

#endif

檢查功能的棧 C 文件更新(stack_with_validate.c)

#include?"stack_with_validate.h"
#include?"stdio.h"
static?int?_push(struct?stack?*p_this,?int?val)
{
????struct?stack_with_validate?*p_stack?=?(struct?stack_with_validate?*)p_this;
????if((p_stack->p_validator?==?NULL)?||?(validator_validate(p_stack->p_validator,?val)?==?0))
????{
????????return?stack_push(p_stack->p_normal_stack,?val);
????}
????return?-1;
}

static?int?_pop(struct?stack?*p_this,?int?*p_val)
{
????struct?stack_with_validate?*p_stack?=?(struct?stack_with_validate?*)p_this;
????return?stack_pop(p_stack->p_normal_stack,?p_val);
}

struct?stack?*?stack_with_validate_init(struct?stack_with_validate?*p_stack,
????????????????????????????????????????struct?stack?*p_normal_stack,
????????????????????????????????????????struct?validator?*p_validator)
{
????stack_init(&p_stack->super,?_push,?_pop);

????p_stack->p_validator?=?p_validator;
????p_stack->p_normal_stack?=?p_normal_stack;
????return?&p_stack->super;
}

基于“帶檢查功能的?!钡膶?shí)現(xiàn),測(cè)試范例如下:

#include?"stack_normal.h"
#include?"stack_with_validate.h"
#include?"validator_range_check.h"

int?main()
{
????int?buf[20];
????struct?stack_normal?stack;
????struct?stack_with_validate?stack_with_validate;
????struct?validator_range_check?validator_range_check;

????struct?stack?*p_stack_normal?=?stack_normal_init(&stack,?buf,?20);

????struct?validator?*p_validator?=?validator_range_check_init(&validator_range_check,?1,?9);
????struct?stack?*p_stack?=?stack_with_validate_init(&stack_with_validate,
??????????????????????????????p_stack_normal,
??????????????????????????????p_validator);

????stack_application(p_stack);
????return?0;
}

由此可見,無論底層的各種棧如何實(shí)現(xiàn),對(duì)于上層應(yīng)用來講,其可以使用同一套接口stack_application操作各種各樣不同的棧。

多種多態(tài)示例的核心解決方案都是相同的,即:定義抽象方法(函數(shù)指針),使上層應(yīng)用可以使用同一套接口訪問不同的對(duì)象。從類的角度看,每個(gè)類中操作的規(guī)約都是相同的,而這些類可以用不同的方式實(shí)現(xiàn)這些同名的操作,從而使得擁有相同接口的對(duì)象可以在運(yùn)行時(shí)相互替換。

同樣的應(yīng)用程序,可以在多個(gè)硬件平臺(tái)上運(yùn)行,更換硬件時(shí)應(yīng)用程序無需作任何改動(dòng)。在嵌入式系統(tǒng)中,相同功能芯片的更新替換,也是多態(tài)應(yīng)用最多的場(chǎng)景,根據(jù)硬件差異多態(tài)封裝,應(yīng)用層無感使用相同接口?;诙鄳B(tài)的思想實(shí)現(xiàn)“與硬件無關(guān)”的應(yīng)用程序,還可以衍生出兩個(gè)概念:抽象接口與依賴倒置,它們的核心都是多態(tài)。更多編碼原則可參考《嵌入式軟件設(shè)計(jì)原則隨想》、《Unix哲學(xué)之編程原則》,分層架構(gòu)《嵌入式軟件分層隔離的典范》。

5 小節(jié)

學(xué)會(huì)了屠龍技,但是沒有龍,怎么辦?有些東西只是一種思維模式,作為日常開發(fā)工作中潛移默化的一種偏愛。所以嵌入式軟件開發(fā)究竟有沒對(duì)象呢?有但少。

推薦器件

更多器件
器件型號(hào) 數(shù)量 器件廠商 器件描述 數(shù)據(jù)手冊(cè) ECAD模型 風(fēng)險(xiǎn)等級(jí) 參考價(jià)格 更多信息
CC430F5137IRGZ 1 Texas Instruments 16-Bit ultra-low-power CC430 Sub 1 GHz wireless MCU with 12-Bit ADC, 32kB Flash and 4kB RAM 48-VQFN -40 to 85

ECAD模型

下載ECAD模型
$7.53 查看
S29AL016J70TFI020 1 Cypress Semiconductor Flash, 1MX16, 70ns, PDSO48, TSOP-48
$10.3 查看
LAN8720AI-CP-ABC 1 Microchip Technology Inc Ethernet Transceiver

ECAD模型

下載ECAD模型
$1.26 查看

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

嵌入式系統(tǒng)開發(fā)技術(shù)交流,軟件開發(fā)的思路與方案共享,行業(yè)資訊的分享。