大家好,我是雜燴君。
嵌入式 / 單片機(jī)項(xiàng)目開發(fā)中,我們常常會(huì)根據(jù)實(shí)際情況選擇大方向的軟件框架:裸機(jī)系統(tǒng)、前后臺(tái)系統(tǒng)、RTOS、Linux等。實(shí)際開發(fā)中,選擇什么樣的軟件架構(gòu),只是第一步。
系統(tǒng)里面的各個(gè)模塊怎么協(xié)同工作,業(yè)務(wù)邏輯怎么設(shè)計(jì)?也得在項(xiàng)目前期好好考慮,如果是想到哪寫到哪,可能后面會(huì)受很多苦。
我們有必要學(xué)習(xí)各種編程模型了(設(shè)計(jì)模式)。如事件驅(qū)動(dòng)編程模型、消息驅(qū)動(dòng)編程模型、狀態(tài)機(jī)模型等。
編程模型與大方向的軟件框架并不沖突,如我們常用的狀態(tài)機(jī)模型,裸機(jī)系統(tǒng)也能跑、RTOS也能跑、Linux也能跑。
本篇筆記我們一起來學(xué)習(xí)MVC編程模型。
1、MVC編程模型簡介
MVC(Model-View-Controller)是一種軟件設(shè)計(jì)模式,它將應(yīng)用程序分為三個(gè)主要部分:模型(Model)、視圖(View)和控制器(Controller),這種分離有助于提高代碼的可維護(hù)性、可擴(kuò)展性和可測試性。
模型(Model):專注于數(shù)據(jù)管理和業(yè)務(wù)邏輯。
視圖(View):負(fù)責(zé)呈現(xiàn)數(shù)據(jù)給用戶,它是用戶界面的部分。
控制器(Controller):作為模型和視圖之間的橋梁,接收用戶的輸入請(qǐng)求,根據(jù)請(qǐng)求調(diào)用相應(yīng)的模型方法進(jìn)行數(shù)據(jù)處理,然后選擇合適的視圖將處理結(jié)果展示給用戶。
MVC 最初是為 Web 應(yīng)用程序設(shè)計(jì)的,但它的思想同樣適用于嵌入式系統(tǒng)開發(fā)。
如嵌入式場景中:
模型(Model):處理傳感器數(shù)據(jù)采集與處理。
視圖(View):處理數(shù)據(jù)顯示(如LCD屏渲染、LED狀態(tài)指示)。
控制器(Controller):協(xié)調(diào)輸入(如GUI輸入、中斷處理、用戶按鍵響應(yīng))。
MVC的核心優(yōu)勢:
1、結(jié)構(gòu)圖
單向依賴:Controller操作Model,View監(jiān)聽Model。
解耦設(shè)計(jì):View和Controller不直接交互,Model獨(dú)立于界面邏輯。
2、時(shí)序圖
-
- 用戶輸入(如按鍵)傳遞到Controller。Controller修改Model數(shù)據(jù)(如更新傳感器狀態(tài))。Model通過
notifyObservers()
- 回調(diào)觸發(fā)View更新(非Controller直接調(diào)用View)。View從Model拉取最新數(shù)據(jù)并渲染(如刷新LCD顯示)。
MVC思想應(yīng)用實(shí)例
下面列舉4個(gè)例子,一個(gè)是我們自己編寫的最小MVC demo,另外3個(gè)是Github上嵌入式相關(guān)的使用到MVC思想的項(xiàng)目。
1、MVC demo
嵌入式中,帶GUI界面的產(chǎn)品,會(huì)有這種場景:切換GUI界面,業(yè)務(wù)邏輯讀取本地配置數(shù)據(jù),刷新到對(duì)應(yīng)GUI界面上。
下面我們針對(duì)這種簡單的場景編寫基于一個(gè)簡單的基于pthread庫的Linux多線程應(yīng)用實(shí)例:
控制器模塊:接收用戶輸入的頁面索引,根據(jù)映射關(guān)系獲取對(duì)應(yīng)的配置索引,并向模型模塊發(fā)送讀取配置的請(qǐng)求。
模型模塊:根據(jù)接收到的配置索引,從配置數(shù)組中獲取相應(yīng)的配置信息,并發(fā)送給視圖模塊。
視圖模塊:接收到配置信息后,將其顯示在對(duì)應(yīng)的頁面上。
(1)類圖
類定義
Model?類負(fù)責(zé)處理配置數(shù)據(jù)的讀取和存儲(chǔ),包含配置數(shù)組、線程和消息隊(duì)列等成員。
View?類負(fù)責(zé)顯示頁面和配置信息,包含當(dāng)前頁面索引、線程和消息隊(duì)列等成員。
Controller?類作為協(xié)調(diào)者,持有?Model?和?View?的指針,負(fù)責(zé)接收用戶輸入并控制它們的交互。
Config?類表示配置信息,包含配置名稱和值。
關(guān)聯(lián)關(guān)系
Controller 與 Model、View 之間是聚合關(guān)系(has),即 Controller 持有 Model 和 View 的實(shí)例。Model 與 Config 之間是組合關(guān)系(contains),Model 包含多個(gè) Config 實(shí)例。
(2)時(shí)序圖
(3)運(yùn)行
- 終端輸入頁面索引。Controller 接收到輸入后,根據(jù)頁面 - 配置映射關(guān)系獲取對(duì)應(yīng)的配置索引。Controller 向 Model 發(fā)送 READ_CONFIG 消息,請(qǐng)求讀取指定配置。Model 從 Config 中獲取相應(yīng)的配置信息。Model 向 View 發(fā)送 CONFIG 消息,傳遞配置信息。View 接收到消息后,顯示當(dāng)前頁面和配置信息。
(4)代碼
#include<stdio.h>
#include"model.h"
#include"view.h"
#include"controller.h"
intmain(void)
{
? ? Model *model = create_model();
? ? View *view = create_view();
? ? Controller *controller = create_controller(model, view);
? ? start_model(model);
? ? start_view(view);
? ? start_controller(controller);
? ? pthread_exit(NULL);
? ??return0;
}
model.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include"model.h"
staticvoidupdate_config(Model *model,?int?config_index)
{
? ??const?Config *config = get_config(model->configs, config_index, model->num_configs);
? ??if?(config)?
? ? {
? ? ? ??char?msg[MAX_MSG_SIZE] = {0};
? ? ? ??snprintf(msg,?sizeof(msg),?"CONFIG:%s:%d", config->name, config->value);
? ? ? ? send_message(model->mq, msg);
? ? }
}
void*?model_thread_func(void?*arg)
{
? ? Model *model = (Model *)arg;
? ??char?buf[MAX_MSG_SIZE] = {0};
? ??while?(1)?
? ? {
? ? ? ??ssize_t?bytes_received = receive_message(model->mq, buf);
? ? ? ? buf[bytes_received] =?'';
? ? ? ??
? ? ? ??if?(strncmp(buf,?"READ_CONFIG:",?12) ==?0)?
? ? ? ? {
? ? ? ? ? ??int?config_index = atoi(buf +?12);
? ? ? ? ? ? update_config(model, config_index);
? ? ? ? }
? ? }
? ??returnNULL;
}
Model*?create_model(void)
{
? ? Model *model = (Model *)malloc(sizeof(Model));
? ??if?(model ==?NULL)?
? ? {
? ? ? ? perror("malloc");
? ? ? ??exit(EXIT_FAILURE);
? ? }
? ? model->num_configs = MAX_CONFIGS;
? ? init_configs(model->configs, model->num_configs);
? ? model->mq = init_message_queue();
? ??return?model;
}
voidstart_model(Model *model)
{
? ??if?(pthread_create(&model->thread,?NULL, model_thread_func, model) !=?0)?
? ? {
? ? ? ? perror("pthread_create");
? ? ? ??exit(EXIT_FAILURE);
? ? }
}
voidstop_model(Model *model)
{
? ? pthread_join(model->thread,?NULL);
? ? close_message_queue(model->mq);
? ??free(model);
}
view.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"view.h"
staticvoiddisplay_page(View *view,?constchar?*config_name,?int?config_value)
{
? ??printf("當(dāng)前頁面: %dn", view->current_page);
? ??printf("業(yè)務(wù)配置: %s, 值: %dn", config_name, config_value);
}
void*?view_thread_func(void?*arg)
{
? ? View *view = (View *)arg;
? ??char?buf[MAX_MSG_SIZE] = {0};
? ??while?(1)?
? ? {
? ? ? ??ssize_t?bytes_received = receive_message(view->mq, buf);
? ? ? ? buf[bytes_received] =?'';
? ? ? ??if?(strncmp(buf,?"CONFIG:",?7) ==?0)?
? ? ? ? {
? ? ? ? ? ??char?config_name[32] = {0};
? ? ? ? ? ??int?config_value =?0;
? ? ? ? ? ??sscanf(buf +?7,?"%[^:]:%d", config_name, &config_value);
? ? ? ? ? ? display_page(view, config_name, config_value);
? ? ? ? }
? ? }
? ??returnNULL;
}
View*?create_view(void)
{
? ? View *view = (View *)malloc(sizeof(View));
? ??if?(view ==?NULL)?
? ? {
? ? ? ? perror("malloc");
? ? ? ??exit(EXIT_FAILURE);
? ? }
? ? view->current_page =?1;
? ? view->mq = init_message_queue();
? ??return?view;
}
voidstart_view(View *view)
{
? ??if?(pthread_create(&view->thread,?NULL, view_thread_func, view) !=?0)?
? ? {
? ? ? ? perror("pthread_create");
? ? ? ??exit(EXIT_FAILURE);
? ? }
}
voidstop_view(View *view)
{
? ? pthread_join(view->thread,?NULL);
? ? close_message_queue(view->mq);
? ??free(view);
}
controller.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include"controller.h"
#include"message_queue.h"
#define?PAGE_CONFIG_MAP_SIZE 10
staticint?page_config_map[PAGE_CONFIG_MAP_SIZE] = {0};
staticvoidinit_page_config_map(void)
{
? ??for?(int?i =?0; i < PAGE_CONFIG_MAP_SIZE; i++)?
? ? {
? ? ? ? page_config_map[i] = i;
? ? }
}
void*?controller_thread_func(void?*arg)
{
? ? Controller *controller = (Controller *)arg;
? ??char?input[10] = {0};
? ? init_page_config_map();
? ??while?(1)?
? ? {
? ? ? ??printf("輸入頁面索引 (0 - %d) 顯示對(duì)應(yīng)頁面配置,輸入 'q' 退出程序: ", PAGE_CONFIG_MAP_SIZE -?1);
? ? ? ? fgets(input,?sizeof(input),?stdin);
? ? ? ??if?(input[0] >=?'0'?&& input[0] <=?'9')?
? ? ? ? {
? ? ? ? ? ??int?page_index = atoi(input);
? ? ? ? ? ??if?(page_index >=?0?&& page_index < PAGE_CONFIG_MAP_SIZE)?
? ? ? ? ? ? {
? ? ? ? ? ? ? ? controller->view->current_page = page_index;
? ? ? ? ? ? ? ??int?config_index = page_config_map[page_index];
? ? ? ? ? ? ? ??char?msg[MAX_MSG_SIZE] = {0};
? ? ? ? ? ? ? ??snprintf(msg,?sizeof(msg),?"READ_CONFIG:%d", config_index);
? ? ? ? ? ? ? ? send_message(controller->model->mq, msg);
? ? ? ? ? ? }?
? ? ? ? ? ??else
? ? ? ? ? ? {
? ? ? ? ? ? ? ??printf("無效的頁面索引,請(qǐng)重新輸入。n");
? ? ? ? ? ? }
? ? ? ? }?
? ? ? ??elseif?(input[0] ==?'q')?
? ? ? ? {
? ? ? ? ? ? stop_model(controller->model);
? ? ? ? ? ? stop_view(controller->view);
? ? ? ? ? ? close_message_queue(controller->mq);
? ? ? ? ? ??exit(0);
? ? ? ? }
? ? }
? ??returnNULL;
}
Controller*?create_controller(Model *model, View *view)
{
? ? Controller *controller = (Controller *)malloc(sizeof(Controller));
? ??if?(controller ==?NULL)?
? ? {
? ? ? ? perror("malloc");
? ? ? ??exit(EXIT_FAILURE);
? ? }
? ? controller->model = model;
? ? controller->view = view;
? ? controller->mq = init_message_queue();
? ??return?controller;
}
voidstart_controller(Controller *controller)
{
? ??if?(pthread_create(&controller->thread,?NULL, controller_thread_func, controller) !=?0)?
? ? {
? ? ? ? perror("pthread_create");
? ? ? ??exit(EXIT_FAILURE);
? ? }
}
voidstop_controller(Controller *controller)
{
? ? pthread_join(controller->thread,?NULL);
? ? close_message_queue(controller->mq);
? ??free(controller);
}
篇幅有限,為了不影響閱讀體驗(yàn)。其它代碼不再貼出。有興趣的朋友可在本公眾號(hào)聊天界面輸入關(guān)鍵詞:MVC例子,即可獲取下載鏈接。
2、Q-Controllers
Q-Controllers是一個(gè)事件驅(qū)動(dòng)的應(yīng)用代碼框架,適用于低端單片機(jī)無法跑操作系統(tǒng),但又要處理越來越復(fù)雜的代碼構(gòu)架的情況。
https://github.com/q-iot/q-controllers
其借鑒MVC模式,提出D-IO-C分層架構(gòu)(Data-IO-Controller),將代碼分為:
Data:數(shù)據(jù)存儲(chǔ)與管理(類似Model層)
IO:輸入輸出硬件操作(如傳感器、顯示屏驅(qū)動(dòng))
Controller:事件驅(qū)動(dòng)的業(yè)務(wù)邏輯處理
3、IotSensorDetect
一個(gè)基于MVC模式 + 狀態(tài)設(shè)計(jì)模式的物聯(lián)網(wǎng)氣體檢測開源項(xiàng)目。
https://github.com/Yangyuanxin/IotSensorDetect
MVC模式:
Model:傳感器數(shù)據(jù)采集與狀態(tài)管理(如ModelSensorHandlerTask)
View:數(shù)據(jù)展示(通過云平臺(tái)或LCD屏)
Controller:指令處理與狀態(tài)協(xié)調(diào)(如ControllerTask)
4、awtk的計(jì)算器例子
基于MVC開發(fā)的計(jì)算器:https://github.com/zlgopen/awtk-patterns/tree/master/src/calculator-mvc
Q & A
Q1: 業(yè)務(wù)邏輯應(yīng)該寫在Controller還是Model?
A:應(yīng)該寫在Model。
已上就是本次關(guān)于MVC模型的分享,如果覺得文章有幫助,麻煩幫忙轉(zhuǎn)發(fā),謝謝!