接上集《嵌入式軟件開發(fā)的對象在哪(上)》繼續(xù)...
4 多態(tài)
多態(tài)字面含義就是具有“多種形式”。從調(diào)用者的角度看對象,會發(fā)現(xiàn)它們非常相似,但內(nèi)部處理實際上卻各不相同。換句話說,各對象雖然內(nèi)部處理不同,但對于使用者(調(diào)用者)來講,它們卻是相同的。
4.1 學生的“自我介紹”
在前面提到的學生類,包含姓名、學號、性別、身高、體重等屬性,并對外提供了一個“自我介紹”方法。
//微信公眾號:嵌入式系統(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è)一個場景,開學第一課所有同學依次作一個簡單的自我介紹,調(diào)用所有同學的自我介紹方法即可,范例程序如下:
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ù)前,需要將所有學生對象創(chuàng)建好,并存于一個數(shù)組中,假定一個班級有 50個學生,則調(diào)用示意代碼如下:
int?main()
{
????struct?student?student[50];
????/*根據(jù)每個學生的信息,依次創(chuàng)建各個學生對象*/
????student_init(&student[0],?"zhangsan",?2024001,?'M',?173,?60);
????student_init(&student[1],?"lisi",?2024002,?'F',?168,?65);
?//?...
????/*上第一節(jié)課????*/
????first_class(student,?50);
}
上面的實現(xiàn)代碼,假定了學生的“自我介紹”格式是完全相同的,都是將個人信息陳述一遍,顯然,這樣的自我介紹無法體現(xiàn)每個學生的個性和差異。例如,一個名叫張三的學生,其期望這樣介紹自己:
“親愛的老師,同學們!我叫張三,來自湖北仙桃,是一個自信開朗,積極向上的人,我有著廣泛的興趣愛好,喜歡打籃球、看書、下棋、聽音樂……”
每個學生自我介紹的內(nèi)容并不期望千篇一律。若不基于多態(tài)的思想,最簡單粗暴的方式是每個學生都提供一個自我介紹方法,例如 student_zhangsan_introduction()。這種情況下每個學生提供的方法都不相同(函數(shù)名不同),根本無法統(tǒng)一調(diào)用,此時,第一節(jié)課的調(diào)用將會大改,需要依次調(diào)用每個學生提供的不同的自我介紹方法,例如:
void?first_class()
{
????student_zhangsan_introduction(&zhangshan);?//?張三自我介紹
????student_lisi_introduction(&lisi);???//?李四自我介紹
?//?….
}
無法使用同樣的調(diào)用形式(函數(shù))完成不同對象的“自我介紹”。對于調(diào)用者來講,需要關(guān)注每個對象提供的特殊方法,復(fù)雜度將提升。
使用多態(tài)的思想即可很好的解決這個問題,進而保證 firstt_class()的內(nèi)容不變,雖然每個對象方法的實現(xiàn)不同,但可以使用同樣的形式調(diào)用它。在 C 語言中,函數(shù)指針就是解決這個問題的“利器”。
函數(shù)指針的原型決定了調(diào)用方法,例如定義函數(shù)指針:
int?(*student_self_introduction)?(struct?student?*p_student);
無論該函數(shù)指針指向何處,都表示該函數(shù)指針指向的是 int 類型返回值,具有一個*p_student 參數(shù)的函數(shù),其調(diào)用形式如下:
student_self_introduction(p_student);
函數(shù)指針的指向代表了函數(shù)的實現(xiàn),指向不同的函數(shù)就代表了不同的實現(xiàn)。基于此,為了使每個學生對象可以有自己獨特的介紹方式,在學生類的定義中,可以不實現(xiàn)自我介紹方法,但可以通過函數(shù)指針約定自我介紹方法的調(diào)用形式。更新學生類的定義:
student.h?文件```
```c
//微信公眾號:嵌入式系統(tǒng)
#ifndef?__STUDENT_H
#define?__STUDENT_H
struct?student
{
????int?(*student_self_introduction)(struct?student?*p_student);??/*?新增個性化自我介紹?*/
????char?name[10];??/*?姓名?(假定最長?10?字符)*/
????unsigned?int?id;?/*?學號?*/
????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?*));
/*?學生類提供的自我介紹方法?*/
static?inline?int?student_self_introduction(struct?student?*p_student)
{
????return?p_student->student_self_introduction(p_student);
}
#endif
此時,對于外界來講,學生類“自我介紹方法”的調(diào)用形式并未發(fā)生任何改變,函數(shù)原型還是一樣的(由于只有一行代碼,因而以內(nèi)聯(lián)函數(shù)的形式存放到了頭文件中)?;诖?,“第一節(jié)課的內(nèi)容”可以保持完全不變(for循環(huán)調(diào)用全部)。在這種方式下,每個對象在初始化時,需要指定自己特殊的自我介紹方,例如張三對象的創(chuàng)建過程為:
int?student_zhangsan_introduction(struct?student?*p_student)
{
????const?char?*str?=?"親愛的老師,同學們!我叫張三,來自湖北仙桃,是一個自信開朗,積極向上的人,我有著廣泛的興趣愛好,喜歡打籃球、看書、下棋、聽音樂……";
????printf("%sn",?str);
????return?0;
}
int?main()
{
????struct?student?student[50];
????/*?根據(jù)每個學生的信息,依次創(chuàng)建各個學生對象?*/
????student_init(&student[0],?"zhangsan",?2024001,?'M',?173,?60,?student_zhangsan_introduction);
????//?...
????/*?上第一節(jié)課?*/
????first_class(student,?50);
}
多態(tài)的核心是:對于上層調(diào)用者,不同的對象可以使用完全相同的操作方法,但是每個對象可以有各自不同的實現(xiàn)方式。多態(tài)是面向?qū)ο缶幊谭浅V匾奶匦?,C 語言依賴指針實現(xiàn)多態(tài)。
(微信公眾號【嵌入式系統(tǒng)】很多設(shè)計模式或硬件多型號適配都是基于這個基礎(chǔ),可以參考《嵌入式軟件的設(shè)計模式(上)》)。
4.2 I/O 設(shè)備驅(qū)動
C 程序使用 printf()打印日志信息,在 PC 上運行時,日志信息可能輸出到控制臺,而在嵌入式系統(tǒng)中,信息可能通過某個串口輸出。printf()函數(shù)的解釋是輸出信息至 STDOUT(標準輸出)。顯然printf()函數(shù)就具有多態(tài)性,對于用戶來講,其調(diào)用形式是確定的,但內(nèi)部具體輸出信息到哪里,卻會隨著 STDOUT 的不同而不同。
在一些操作系統(tǒng)中(如Linux),硬件設(shè)備(例串口、ADC 等)的操作方法都和文件操作方法類似(一切皆文件),都可以通過 open()、close()、read()、write()等幾個標準函數(shù)進行操作。為統(tǒng)一 I/O 設(shè)備的使用方法,要求每個 I/O 設(shè)備都提供 open、close、read、write 這幾個標準函數(shù)的實現(xiàn),即每個 I/O設(shè)備的驅(qū)動程序,對這些標準函數(shù)的實現(xiàn)在函數(shù)調(diào)用上必須保持一致。這本質(zhì)上就是一個多態(tài)問題,即以同樣的方法使用不同的 I/O 設(shè)備。
通過函數(shù)指針解決這個問題,首先定義file_ops結(jié)構(gòu)體,包含了相對應(yīng)的函數(shù)指針,指向I/O 設(shè)備針對操作的實現(xiàn)函數(shù)。
file_ops.h?文件
//微信公眾號:嵌入式系統(tǒng)
//代碼片段只是原理性展示
struct?file_ops
{
????void?(*open)(char?*name,?int?mode);
????void?(*close)();
????int?(*read)();
????void?(*write)();
};
對于 I/O設(shè)備,其驅(qū)動程序提供這 4個函數(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)生命名沖突。對于該設(shè)備,僅對外提供了一個可以使用的 file_ops 對象 my_console。
上面展示了設(shè)備 I/O 的一般管理方法,其中的編程方法或技巧正是面向?qū)ο缶幊讨卸鄳B(tài)的基礎(chǔ),也再一次展現(xiàn)了函數(shù)指針在多態(tài)中的重要地位,多態(tài)可以視為函數(shù)指針的一種典型應(yīng)用。(微信公眾號【嵌入式系統(tǒng)】類似使用是Linux設(shè)備驅(qū)動的基礎(chǔ))。
4.3 帶檢查功能的棧
前面范例實現(xiàn)了棧的核心邏輯(入棧和出棧),假設(shè)現(xiàn)在增加需求,實現(xiàn)“帶檢查功能的?!?,即在數(shù)據(jù)入棧之前,必須進行特定的檢查,“檢查通過”后才能壓人棧中。檢查方式有多種:
范圍檢查:必須在特定的范圍之內(nèi),比如1 ~ 9,才視為檢查通過;
奇偶檢查:必須是奇數(shù)或者偶數(shù),才視為檢查通過;
變化檢查:值必須增加(比上一次的值大),才視為檢查通過。
4.3.1 基于繼承實現(xiàn)“帶范圍檢查功能”的棧
先不考慮多種檢查方式,僅實現(xiàn)范圍檢查。參照“命名?!钡膶崿F(xiàn),使用繼承方式,在普通棧的基礎(chǔ)上實現(xiàn)一個新類,范例程序如下:
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
//微信公眾號:嵌入式系統(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))??//差異點
????{
????????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);
}
為了接口的簡潔性,沒有再展示解初始化等函數(shù)的定義。新增入棧時作檢查,出棧和普通棧是完全相同的,但基于最小知識原則也封裝了一個 pop 接口,使該類的用戶完全不需要關(guān)心普通棧。
依照這個方法,可以實現(xiàn)其它檢查方式的棧。核心是實現(xià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);?//檢查通過:偶校驗且為偶數(shù),或奇校驗且為奇數(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;
}
由此可見,這種實現(xiàn)方式存在一定的缺陷,不同檢查方法對應(yīng)的入棧函數(shù)不相同,對于用戶來講,使用不同的檢查功能,就必須調(diào)用不同的入棧函數(shù)。即操作不同的棧使用不同的接口。但觀察幾個入棧函數(shù),其入棧方法類似,示意代碼如下:
int?stack_XXX_push(struct?stack_XXX?*p_stack,?int?val)
{
????if(檢查通過)??//不同棧的差異僅是檢測條件不同
????{
????????return?stack_push(&p_stack->super,?val);
????}
????return?-1;
}
可使用多態(tài)思想,將“檢查”函數(shù)的調(diào)用形式標準化編寫一個通用的、與具體檢查方式無關(guān)的入棧函數(shù)。
4.3.2 基于多態(tài)實現(xiàn)通用的“帶檢查功能的?!?/h4>
使用函數(shù)指針表示“檢查功能”,指向不同的檢查函數(shù)??梢远x一個包含函數(shù)指針的類:
struct?stack_with_validate
{
????struct?stack?super;????????????/*?基類(超類)*/
????int?(*validate)(struct?stack_with_validate?*p_this,?int?val);??/*?檢查函數(shù)?*/
};
和其它普通方法一樣,類中抽象方法(函數(shù)指針)的第一個成員同樣是指向該類對象的指針。此時,數(shù)據(jù)入棧前的檢查工作交給 validate 指針所指向的函數(shù)實現(xiàn)。假定其指向的函數(shù)在檢查數(shù)據(jù)時,返回 0 表示檢查通過可入棧,其它值表示檢查未通過。完整的帶檢查功能的棧實現(xiàn)范例如下:
帶檢查功能的棧 H 文件(stack_with_validate.h)
//微信公眾號:嵌入式系統(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);
}
帶某種檢查功能的棧,重點是實現(xiàn)其中的 validate 方法?;趲z查的棧,實現(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;
}
帶范圍檢查的棧,主要目的就是實現(xiàn)“檢查功能”對應(yīng)的函數(shù):_validate,并將其作為 validate 函數(shù)指針(抽象方法)的值。
在面向?qū)ο缶幊讨?,包含抽象方法的類通常稱之為抽象類,抽象類不能直接實例化(因為其還有方法未實現(xiàn)),抽象類只能被繼承,且由子類實現(xiàn)其中定義的抽象方法。在 UML 類圖中,抽象類的類名和其中的抽象方法均使用斜體表示,普通棧、帶檢查功能的棧和帶范圍檢查的棧,它們之間的關(guān)系詳見圖。
帶范圍檢查的棧,其主要作用是實現(xiàn)其父類中定義的抽象方法,進而創(chuàng)建一個真正的“帶檢查功能”的棧對象(此時的抽象方法已實現(xiàn)),該對象即可提交給外部使用。帶范圍檢查的棧并沒有其他特殊的方法,因而在其初始化完成后,通過初始化函數(shù)的返回值向外界提供了一個“帶檢查功能”的棧對象,后續(xù)用戶即可使用 stack_with_validate.h 文件中的push 和 pop 方法操作該對象。
帶范圍檢查的棧使用范例如下:
//微信公眾號:嵌入式系統(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)建“帶檢查功能”的棧對象(完成抽象方法的實現(xiàn))。創(chuàng)建完畢后,對于用戶操作方法都是完全相同的 stack_with_validate_push 和 stack_with_validate_pop ,與檢查方式無關(guān)。為避免贅述,這里不再實現(xiàn)另外兩種檢查功能的棧,僅展示出他們的類圖。
在這里插入圖片描述
在一些大型項目中,初始化過程往往和應(yīng)用程序是分離的(即stack_with_range_check_init 內(nèi)部封閉不可見),也就是說,對于用戶來講,其僅會獲取到一個 struct stack_with_validate *類型的指針,其指向某個“帶檢查功能的?!?,實際檢查什么,用戶可能并不關(guān)心,應(yīng)用程序基于該類型指針編程,將使應(yīng)用程序與具體檢查功能無關(guān),即使后續(xù)更換為其它檢查方式,應(yīng)用程序也不需要做任何改動。
4.4 抽象分離
如果是硬件資源有限,功能單一或大概率無需擴展的嵌入式軟件開發(fā),進行到這基本可以滿足需求;如果是復(fù)雜應(yīng)用,且硬件資源充足還可繼續(xù)優(yōu)化。
4.4.1 檢查功能抽象
前面的實現(xiàn)中,將檢查功能視為棧的一種擴展(使用繼承),檢查邏輯直接在相應(yīng)的擴展類中實現(xiàn)。這就使檢查功能與棧綁定在一起,檢查功能的實現(xiàn)無法獨立復(fù)用。如果要實現(xiàn)一個“帶檢查功能的隊列”,同樣是上述的 3 種檢查邏輯,期望能夠復(fù)用檢查邏輯相關(guān)的代碼。顯然,由于當前檢查邏輯的實現(xiàn)與棧捆綁在一起,無法單獨提取出來復(fù)用。
檢查功能與棧的綁定,主要在“帶檢查功能的?!敝畜w現(xiàn),該類的定義如下:
struct?stack_with_validate
{
????struct?stack??super;???/*?基類(超類)*/
????int?(*validate)(struct?stack_with_validate?*p_this,?int?val);?/*?檢查函數(shù)?*/
};
super 用于繼承自普通棧,validate 表示一個抽象的數(shù)據(jù)檢查方法,不同的檢查方法,通過該指針所指向的函數(shù)體現(xiàn)。由于檢查方法validate是該類的一個方法,檢查邏輯與棧綁定。為了解綁分離,可以將檢查邏輯放到獨立的與棧無關(guān)的類中,額外定義一個抽象的校驗器類,專門表示數(shù)據(jù)檢查邏輯:
struct?validator
{
????int?(*validate)(struct?validator?*p_this,?int?val);?/*?檢查函數(shù)?*/
};
雖然該類僅包含 validate 函數(shù)指針,但需注意該函數(shù)指針類型的變化,其第一個參數(shù)為指向校驗器的指針,而在“帶檢查功能的?!敝?,其第一個參數(shù)是指向“帶檢查功能的?!钡闹羔?。通過該類的定義,明確的將檢查邏輯封裝到獨立的校驗器類中,與棧再無任何關(guān)聯(lián)。不同的檢查邏輯,可以在其子類中實現(xiàn),校驗器類和各個子類之間的關(guān)系如下:由于校驗器類僅包含一個函數(shù)指針,因此其只需要在頭文件中定義出類即可,程序如下:
校驗器類定義(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)??/*?校驗函數(shù)?*/
{
????if(p_validator->validate?==?NULL)??/*?校驗函數(shù)為空,視為無需校驗?*/
????{
????????return?0;
????}
????return?p_validator->validate(p_validator,?val);
}
#endif
初始化函數(shù)負責為 validate 賦值,validator_validate 函數(shù)是校驗器對外提供的校驗函數(shù),在其實現(xiàn)中僅調(diào)用了 validate 函數(shù)指針指向的函數(shù)。由于函數(shù)都比較簡單,因而直接使用了內(nèi)聯(lián)函數(shù)的形式進行了定義。接下來以范圍校驗為例,實現(xià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
范圍校驗器 C 文件內(nèi)容(validator_range_check.c)
//微信公眾號:嵌入式系統(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 類僅用于實現(xiàn) validator 抽象類中定義的抽象方法,其初始化函數(shù)可以直接對外返回一個標準的校驗器(其中的抽象方法已實現(xiàn))。按照同樣的方法,可以實現(xiàn)validator_oddeven_check 類和 validator_change_check 類。將檢查功能從“帶檢查功能的?!敝蟹蛛x出來之后,“帶檢查功能的?!敝芯蜔o需再維護檢查功能對應(yīng)的抽象方法。其可以通過依賴的方式使用檢查功能,即依賴一個校驗器。在類圖中,依賴關(guān)系可以使用一個虛線箭頭表示,箭頭指向被依賴的類,示意圖如下:“帶檢查功能的?!鳖惗x如下:
struct?stack_with_validate
{
????struct?stack?super;????/*?基類(超類)*/
????struct?validator?*p_validator;?/*?依賴的校驗器?*/
};
與先前相比,其核心變化是由一個 validate 函數(shù)指針(指向具體的檢查方法)變更為 p_validator 指針(指向抽象的檢查方法),變化雖小,但是兩種截然不同的設(shè)計理念。之前的方式是定義了一個抽象方法,而現(xiàn)在的方式是依賴于一個校驗器對象。
基于此更新“帶檢查功能的?!鳖惖膶崿F(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)
//微信公眾號:嵌入式系統(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ù)用,測試更新后的帶檢查功能的棧:
#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;
????/*?獲取范圍檢查校驗器?*/
????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);//使用和先前繼承方式一樣,實現(xiàn)忽略
?
????return?0;
}
4.4.2 ?定義抽象棧
定義校驗器類后,整個系統(tǒng)實現(xiàn)了兩種棧:普通棧和“帶檢查功能的?!?,無論什么棧,對于用戶來講都是實現(xiàn)入棧和出棧兩個核心邏輯。兩種棧提供兩種入棧和出棧方法。
普通棧提供的方法為:
int?stack_push(struct?stack?*p_stack,?int?val);??/*?入棧?*/
int?stack_pop(struct?stack?*p_stack,?int?*p_val);?/*?出棧?*/
“帶檢查功能的?!碧峁┑姆椒椋?/p>
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ù)指針),則可以達到這樣的效果:無論使用何種棧,都可以使用相同的方法來實現(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
基于抽象棧的定義,使用抽象棧提供的接口實現(xiàn)一個通用的應(yīng)用程序,該應(yīng)用程序與底層細節(jié)無關(guān),任何棧都可以使用該應(yīng)用程序進行測試。
基于抽象棧實現(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)用層代碼再有底層代碼。在實現(xiàn)具體棧之前,就可以開始編寫應(yīng)用程序(微信公眾號【嵌入式系統(tǒng)】這就是依賴倒置原則,可參考《嵌入式軟件設(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)
//微信公眾號:嵌入式系統(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;
}
基于普通類的實現(xiàn),測試普通棧類:
#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ǔ)上,增加了檢查功能,實現(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;??/*?依賴于普通棧的實現(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;
}
基于“帶檢查功能的?!钡膶崿F(xiàn),測試范例如下:
#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;
}
由此可見,無論底層的各種棧如何實現(xiàn),對于上層應(yīng)用來講,其可以使用同一套接口stack_application操作各種各樣不同的棧。
多種多態(tài)示例的核心解決方案都是相同的,即:定義抽象方法(函數(shù)指針),使上層應(yīng)用可以使用同一套接口訪問不同的對象。從類的角度看,每個類中操作的規(guī)約都是相同的,而這些類可以用不同的方式實現(xiàn)這些同名的操作,從而使得擁有相同接口的對象可以在運行時相互替換。
同樣的應(yīng)用程序,可以在多個硬件平臺上運行,更換硬件時應(yīng)用程序無需作任何改動。在嵌入式系統(tǒng)中,相同功能芯片的更新替換,也是多態(tài)應(yīng)用最多的場景,根據(jù)硬件差異多態(tài)封裝,應(yīng)用層無感使用相同接口?;诙鄳B(tài)的思想實現(xiàn)“與硬件無關(guān)”的應(yīng)用程序,還可以衍生出兩個概念:抽象接口與依賴倒置,它們的核心都是多態(tài)。更多編碼原則可參考《嵌入式軟件設(shè)計原則隨想》、《Unix哲學之編程原則》,分層架構(gòu)《嵌入式軟件分層隔離的典范》。
5 小節(jié)
學會了屠龍技,但是沒有龍,怎么辦?有些東西只是一種思維模式,作為日常開發(fā)工作中潛移默化的一種偏愛。所以嵌入式軟件開發(fā)究竟有沒對象呢?有但少。