引言
在我們的嵌入式 C 開發(fā)中經(jīng)常會(huì)面對(duì)這樣的一類需求:因?yàn)閷?duì)接的設(shè)備支持的協(xié)議不同,自身的設(shè)備需要兼容這些協(xié)議,因此需要業(yè)務(wù)支持不同的協(xié)議解析方式。
比如有的協(xié)議用 tlv 的數(shù)據(jù)格式,有的用 xml 的格式,有些又用 json 這樣的格式,面對(duì)如此多差異化的需求,我們該如何解決呢?
一種常見的做法是將協(xié)議解析和邏輯實(shí)現(xiàn)做到一起,每種協(xié)議對(duì)應(yīng)一套代碼,這種實(shí)現(xiàn)方式簡單,沒有什么設(shè)計(jì)可言,就是擼代碼。
但是每當(dāng)對(duì)接一種新的協(xié)議就要從頭開發(fā),這種重復(fù)造輪子的方式非常初級(jí),代碼的可擴(kuò)展性也非常差,有沒有一種更高級(jí)一點(diǎn)的方法呢?
答案是有的,也就是我們本文要講的嵌入式多態(tài)。
嵌入式多態(tài)原理
我們首先審視一下上面的需求,上述需求的特點(diǎn)是協(xié)議是有多種的數(shù)據(jù)承載格式(tlv/json/xml),需要針對(duì)不同的協(xié)議做解析,但是解析完成后的數(shù)據(jù)處理,也就是業(yè)務(wù)邏輯的部分卻可以保持不變。
因此我們完全可以使用一種模型將這兩者分離開來,將易變的協(xié)議解析部分單獨(dú)作為一個(gè)模塊,將不易變的業(yè)務(wù)邏輯作為一個(gè)模塊。
協(xié)議解析模塊主要負(fù)責(zé)生成數(shù)據(jù),而業(yè)務(wù)邏輯模塊就主要對(duì)數(shù)據(jù)進(jìn)行處理,中間鏈接這兩者的模型,我們暫且稱之為數(shù)據(jù)分發(fā)模型,負(fù)責(zé)將協(xié)議解析出的數(shù)據(jù)傳給業(yè)務(wù)邏輯部分。
這樣一來協(xié)議解析模塊就可以在自己內(nèi)部做任何變化,可以對(duì)不同格式的協(xié)議進(jìn)行解析,而這對(duì)于業(yè)務(wù)邏輯來說都是不感知的,代碼的擴(kuò)展性更佳。
要想達(dá)到這種良好的可擴(kuò)展性,我們引入了一個(gè)負(fù)責(zé)數(shù)據(jù)分發(fā)的模型,這個(gè)模型要怎么實(shí)現(xiàn)呢?
我們看到協(xié)議解析其實(shí)是有多種實(shí)現(xiàn)方式,而業(yè)務(wù)邏輯可以看作只有一種實(shí)現(xiàn)方式,這就是一種多對(duì)一的方式。
在數(shù)據(jù)分發(fā)模型中,負(fù)責(zé)將多種協(xié)議解析方式收斂成一種,我們想一下C語言中哪種方式可以實(shí)現(xiàn)這種多對(duì)一的收斂呢?沒錯(cuò),就是函數(shù)指針。
這是指針的一種非常高級(jí)的應(yīng)用,它可以將函數(shù)的框架抽象出來,用指針的方式進(jìn)行定義,我們知道指針可以初始化指向不同的地址,那函數(shù)指針就可以被指向多個(gè)不同的函數(shù)。
當(dāng)我們調(diào)用函數(shù)指針時(shí),就可以動(dòng)態(tài)地切換到不同的函數(shù)實(shí)現(xiàn)上。想一下,雖然協(xié)議解析的方式不同,但是最終業(yè)務(wù)邏輯需要的數(shù)據(jù)是固定的,因此完全可以將函數(shù)的參數(shù)搞成一樣的結(jié)構(gòu)。
在面向?qū)ο蟮?a target="_blank">編程語言中,這種實(shí)現(xiàn)方式稱之為多態(tài),同一個(gè)接口(抽象級(jí),沒有具體實(shí)現(xiàn))可以支持不同的具體實(shí)現(xiàn)。在我們嵌入式C開發(fā)中需要自己寫這種多態(tài)的接口,但是也不難,筆者將一個(gè)模板寫在了下一節(jié)中,讀者可以參照這種寫法,靈活修改。
嵌入式多態(tài)偽代碼實(shí)現(xiàn)模板
定義數(shù)據(jù)分發(fā)的模型,抽象接口
1)偽代碼
?
/*?回調(diào)的函數(shù)鉤子?*/ typedef?int?(*hook_func)(void*,?int?,?void*); typedef?struct? { ?/*?初始化接口,定義諸如內(nèi)存之類的問題?*/ ?void?(*init)(void); ?/*?注冊服務(wù)對(duì)應(yīng)的處理函數(shù),如協(xié)議解析后的業(yè)務(wù)邏輯處理映射?*/ ?int?(*register)(int?type,?hook_func?func); ?/*?將數(shù)據(jù)分發(fā)到對(duì)應(yīng)的服務(wù)?*/ ?int?(*distribute)(void?*input,?int?len,?void?*ret); }demo_param_s;
?
2)代碼解析
在這個(gè)結(jié)構(gòu)體中,我們定義了三個(gè)函數(shù)指針,分別是:
初始化接口,用來初始化內(nèi)存之類,這個(gè)內(nèi)存可以用來存儲(chǔ)我們提供的服務(wù)類型和回調(diào)函數(shù);
注冊服務(wù)處理函數(shù),主要用來建立服務(wù)類型和回調(diào)函數(shù)的映射,在我們的案例中主要將協(xié)議解析后,需要使用哪種業(yè)務(wù)邏輯處理,建立這樣的一個(gè)模型;
數(shù)據(jù)分發(fā)服務(wù),主要將協(xié)議解析后的數(shù)據(jù)作為參數(shù),在服務(wù)映射表中找到對(duì)應(yīng)的回調(diào)函數(shù),然后進(jìn)行回調(diào)。
如下圖所示,可以非常詳細(xì)的描述這個(gè)過程。
數(shù)據(jù)分發(fā)模型的初始化接口
1)偽代碼
?
demo_param_s?*g_demo_param?=?NULL; void?demo_init(const?demo_param_s?*param) { ?g_demo_param?=?param; ?return; }
?
2)代碼解析
上面的這部分代碼非常簡單,首先是定義了一個(gè)全局變量,這個(gè)全局編碼是數(shù)據(jù)分發(fā)模型的類型,用來指向一個(gè)數(shù)據(jù)分發(fā)模型的具體實(shí)現(xiàn),初始時(shí),可以讓這個(gè)全局變量指向空。
接下來,就是定義了一個(gè)初始化接口,這個(gè)接口是來初始化我們定義的這個(gè)全局變量的,你會(huì)看到這個(gè)全局變量指向了一個(gè)輸入進(jìn)來的參數(shù),而這個(gè)參數(shù)就是數(shù)據(jù)分發(fā)模型中各個(gè)函數(shù)指針的具體實(shí)現(xiàn)函數(shù),你將在下一節(jié)中看到。
每個(gè)函數(shù)指針的具體實(shí)現(xiàn)
1)偽代碼
?
void?init_impl(void); int?register_impl(int?type,?hook_func?func); int?distribute_impl(void?*input,?int?len,?void?*ret); demo_param_s?demo_param?=?{.init?=?init_impl, ??????????????.register?=?register_impl, ??????????????.distribute?=?distribute_impl}; demo_init(&demo_param);
?
2)代碼解析
這部分的偽代碼主要是對(duì)3個(gè)函數(shù)指針實(shí)現(xiàn),并進(jìn)行初始化。
首先是三個(gè)具體的實(shí)現(xiàn)函數(shù)被聲明和定義,第一個(gè)初始化函數(shù),就是 malloc 出內(nèi)存;第二個(gè)函數(shù),建立服務(wù)和業(yè)務(wù)處理函數(shù)的映射關(guān)系;第三個(gè)函數(shù),在需要某種服務(wù)時(shí),通過查詢第二個(gè)函數(shù)建立的映射表,回調(diào)具體的業(yè)務(wù);
其次就是定義了一個(gè)數(shù)據(jù)分發(fā)模型的局部變量,然后初始化其中的每個(gè)函數(shù)指針;
最后調(diào)用我們在前面定義的數(shù)據(jù)分發(fā)初始化接口,將上面定義的局部變量的值傳入,使其可以全局訪問,在后面我們就可以直接用全局變量 g_demo_param 加上其中的參數(shù)的方式調(diào)用每個(gè)接口。
通過抽象接口調(diào)用注冊和分發(fā)功能
1)偽代碼
?
//?分發(fā) for(i?=?0;?i?//?注冊 int?type?=?0; int?logic_func(void?*input,?int?len,?void?*ret); ret=?g_demo_param.register(type,?logic_func);?
2)代碼解析
上面就是我們的注冊和分發(fā)的接口,其中注冊的部分是在業(yè)務(wù)邏輯中實(shí)現(xiàn)的,分發(fā)的部分代碼是在協(xié)議解析的部分實(shí)現(xiàn)的。
需要特別注意這個(gè)實(shí)現(xiàn)規(guī)則,因?yàn)橹挥羞@樣才能達(dá)到業(yè)務(wù)邏輯和協(xié)議解析的分離,協(xié)議解析只依賴我們的數(shù)據(jù)分發(fā)模型,業(yè)務(wù)邏輯也只依賴我們的數(shù)據(jù)分發(fā)模型,這兩者之間互不依賴,可以獨(dú)立的編譯或者打包。
首先我們看注冊,有類型和具體函數(shù)實(shí)現(xiàn),我們可以使用 g_demo_param.register 去創(chuàng)建兩者的映射關(guān)系,將其保存在內(nèi)存中。
然后當(dāng)我們解析完協(xié)議,就來到我們的分發(fā)部分,當(dāng)我們的類型在內(nèi)存映射表中有存儲(chǔ)時(shí),就可以使用g_demo_param.distribute 實(shí)現(xiàn)分發(fā),調(diào)用我們的業(yè)務(wù)邏輯代碼。
小結(jié)
隨著嵌入式軟件變得越來越復(fù)雜,架構(gòu)問題也變得越來越突出,市場上我們最多見的是對(duì)互聯(lián)網(wǎng)這類架構(gòu)的介紹,鮮有專門針對(duì)嵌入式軟件的架構(gòu)分析。本文通過一個(gè)案例淺談了嵌入式多態(tài)的實(shí)現(xiàn)方式,目的是幫助讀者在設(shè)計(jì)開發(fā)相關(guān)的場景時(shí)能夠拿來借鑒,使自己的代碼具有更好地?cái)U(kuò)展性。
審核編輯:湯梓紅
評(píng)論