嵌入式C语言高级编程之MVC设计模式
这是一个极简的MVC模式实现,专注于核心概念,代码简洁易懂。
1. 📁 项目结构
minimal_mvc/
├── model.h
├── model.c
├── view.h
├── view.c
├── controller.h
├── controller.c
├── main.c
├── Makefile
└── README.md
2. 📝 代码实现
2.1 模型层 (Model) - 数据与业务逻辑
2.1.1 model.h
c
#ifndef MODEL_H
#define MODEL_H
// 模型数据结构
typedef struct {
int counter; // 计数器值
int max_value; // 最大值
int min_value; // 最小值
} Model;
// 模型接口
void model_init(Model *m);
void model_increment(Model *m);
void model_decrement(Model *m);
int model_get_value(Model *m);
void model_reset(Model *m);
#endif
2.1.2 model.c
c
#include "model.h"
#include <stdio.h>
void model_init(Model *m) {
m->counter = 0;
m->max_value = 10;
m->min_value = -10;
printf("[Model] 初始化完成,初始值: %d\n", m->counter);
}
void model_increment(Model *m) {
if (m->counter < m->max_value) {
m->counter++;
printf("[Model] 值增加: %d\n", m->counter);
} else {
printf("[Model] 已达最大值: %d\n", m->max_value);
}
}
void model_decrement(Model *m) {
if (m->counter > m->min_value) {
m->counter--;
printf("[Model] 值减少: %d\n", m->counter);
} else {
printf("[Model] 已达最小值: %d\n", m->min_value);
}
}
int model_get_value(Model *m) {
return m->counter;
}
void model_reset(Model *m) {
m->counter = 0;
printf("[Model] 值已重置: %d\n", m->counter);
}
2.2 视图层 (View) - 显示界面
2.2.1 view.h
c
#ifndef VIEW_H
#define VIEW_H
#include "model.h"
// 视图接口
void view_init(void);
void view_display(Model *m);
void view_show_message(const char *msg);
#endif
2.2.2 view.c
c
#include "view.h"
#include <stdio.h>
void view_init(void) {
printf("\n");
printf("╔══════════════════════════════╗\n");
printf("║ MVC 计数器示例 ║\n");
printf("╚══════════════════════════════╝\n");
printf("\n");
}
void view_display(Model *m) {
printf("\n");
printf("┌──────────────────────────────┐\n");
printf("│ 当前计数值: %3d │\n", m->counter);
printf("│ 范围: [%d, %d] │\n", m->min_value, m->max_value);
printf("└──────────────────────────────┘\n");
printf("\n");
printf("操作说明:\n");
printf(" [+] 增加 [-] 减少 [r] 重置 [q] 退出\n");
printf("请输入命令: ");
fflush(stdout);
}
void view_show_message(const char *msg) {
printf("\n[消息] %s\n", msg);
}
2.3 控制器层 (Controller) - 业务逻辑协调
2.3.1 controller.h
c
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include "model.h"
// 事件类型
typedef enum {
EVENT_NONE,
EVENT_INCREMENT,
EVENT_DECREMENT,
EVENT_RESET,
EVENT_QUIT
} Event;
// 控制器接口
void controller_init(Model *m);
void controller_handle_event(Event e);
int controller_should_quit(void);
void controller_run(void); // 主循环
#endif
2.3.2 controller.c
c
#include "controller.h"
#include "view.h"
#include <stdio.h>
#include <stdlib.h>
static Model g_model; // 模型实例
static int quit_flag = 0; // 退出标志
void controller_init(Model *m) {
if (m) {
g_model = *m;
} else {
model_init(&g_model);
}
view_init();
quit_flag = 0;
}
void controller_handle_event(Event e) {
int need_refresh = 1;
switch (e) {
case EVENT_INCREMENT:
model_increment(&g_model);
break;
case EVENT_DECREMENT:
model_decrement(&g_model);
break;
case EVENT_RESET:
model_reset(&g_model);
break;
case EVENT_QUIT:
view_show_message("正在退出系统...");
quit_flag = 1;
need_refresh = 0;
break;
default:
need_refresh = 0;
break;
}
if (need_refresh) {
view_display(&g_model);
}
}
int controller_should_quit(void) {
return quit_flag;
}
// 获取用户输入(阻塞式)
static Event get_user_input(void) {
int ch = getchar();
// 清除缓冲区
while (getchar() != '\n');
switch (ch) {
case '+':
case '=':
return EVENT_INCREMENT;
case '-':
return EVENT_DECREMENT;
case 'r':
case 'R':
return EVENT_RESET;
case 'q':
case 'Q':
return EVENT_QUIT;
default:
return EVENT_NONE;
}
}
void controller_run(void) {
view_display(&g_model);
while (!controller_should_quit()) {
Event e = get_user_input();
if (e != EVENT_NONE) {
controller_handle_event(e);
} else {
// 无效输入提示
printf("\n[提示] 无效命令,请使用 + - r q\n");
view_display(&g_model);
}
}
}
2.4 主程序
2.4.1 main.c
c
#include "controller.h"
int main(void) {
// 初始化控制器(内部会初始化模型和视图)
controller_init(NULL);
// 运行主循环
controller_run();
printf("\n程序已退出,再见!\n");
return 0;
}
3. 🔧 Makefile
makefile
# 最小化MVC示例 Makefile
CC = gcc
CFLAGS = -Wall -Wextra -g
TARGET = mvc_demo
SOURCES = main.c model.c view.c controller.c
OBJECTS = $(SOURCES:.c=.o)
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJECTS) $(TARGET)
run: $(TARGET)
./$(TARGET)
help:
@echo "可用命令:"
@echo " make - 编译程序"
@echo " make run - 编译并运行"
@echo " make clean - 清理编译文件"
.PHONY: all clean run help
4. 🚀 编译和运行
bash
# 编译
make
# 运行
make run
# 或者直接运行
./mvc_demo
5. 💡 运行示例
╔══════════════════════════════╗
║ MVC 计数器示例 ║
╚══════════════════════════════╝
┌──────────────────────────────┐
│ 当前计数值: 0 │
│ 范围: [-10, 10] │
└──────────────────────────────┘
操作说明:
[+] 增加 [-] 减少 [r] 重置 [q] 退出
请输入命令: +
[Model] 值增加: 1
┌──────────────────────────────┐
│ 当前计数值: 1 │
│ 范围: [-10, 10] │
└──────────────────────────────┘
操作说明:
[+] 增加 [-] 减少 [r] 重置 [q] 退出
请输入命令: +
[Model] 值增加: 2
┌──────────────────────────────┐
│ 当前计数值: 2 │
│ 范围: [-10, 10] │
└──────────────────────────────┘
操作说明:
[+] 增加 [-] 减少 [r] 重置 [q] 退出
请输入命令: r
[Model] 值已重置: 0
┌──────────────────────────────┐
│ 当前计数值: 0 │
│ 范围: [-10, 10] │
└──────────────────────────────┘
操作说明:
[+] 增加 [-] 减少 [r] 重置 [q] 退出
请输入命令: q
[消息] 正在退出系统...
程序已退出,再见!
6. 📚 MVC模式详解
6.1 架构图
┌─────────────────────────────────────────────┐
│ MAIN │
│ (程序入口和组装) │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ CONTROLLER │
│ (业务逻辑协调者) │
│ - 接收用户输入 │
│ - 调用Model更新数据 │
│ - 通知View刷新显示 │
└──────────┬──────────────────┬───────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ MODEL │ │ VIEW │
│ (数据与逻辑) │ │ (界面显示) │
│ - 存储数据 │ │ - 显示数据 │
│ - 业务规则 │ │ - 用户界面 │
│ - 数据验证 │ │ - 格式化输出 │
└──────────────────┘ └──────────────────┘
6.2 数据流向
6.2.1 用户操作流程
用户输入 → Controller接收 → Model更新 → View刷新 → 用户看到新界面
6.2.2 代码对应关系
c
// 用户输入: '+' 键
// ↓
Event e = get_user_input(); // 返回 EVENT_INCREMENT
// ↓
controller_handle_event(EVENT_INCREMENT);
// ↓
model_increment(&g_model); // Model更新数据
// ↓
view_display(&g_model); // View刷新显示
6.3 各层职责详解
6.3.1 Model (模型层)
职责:
- 存储应用程序数据
- 实现业务逻辑和规则
- 提供数据访问接口
特点:
- 不依赖View和Controller
- 可独立测试
- 数据验证在这里完成
示例代码:
c
void model_increment(Model *m) {
if (m->counter < m->max_value) { // 业务规则
m->counter++; // 数据更新
}
}
6.3.2 View (视图层)
职责:
- 显示数据给用户
- 接收用户输入(通常转给Controller)
- 提供友好的用户界面
特点:
- 只读取Model,不修改
- 可以有不同的显示方式
- 独立于业务逻辑
示例代码:
c
void view_display(Model *m) {
printf("当前值: %d\n", m->counter); // 只读Model
}
6.3.3 Controller (控制器层)
职责:
- 接收用户输入
- 调用Model更新数据
- 触发View刷新
特点:
- 连接Model和View
- 包含应用流程控制
- 协调各组件交互
示例代码:
c
void controller_handle_event(Event e) {
switch (e) {
case EVENT_INCREMENT:
model_increment(&g_model); // 更新Model
view_display(&g_model); // 刷新View
break;
}
}
7. 🎯 关键设计原则
7.1 关注点分离
c
// ❌ 错误示例:混合所有逻辑
void bad_example() {
int counter = 0;
char input;
while(1) {
printf("Counter: %d\n", counter);
input = getchar();
if (input == '+') counter++;
if (input == '-') counter--;
if (input == 'q') break;
}
}
// ✅ MVC正确示例:职责分离
// Model: 存储counter
// View: 显示counter
// Controller: 处理输入并协调
7.2 单向数据流
User Action → Controller → Model → View → User
↑ ↓
└──────────────────────────────────────┘
7.3 依赖方向
Controller → Model
Controller → View
Model ↛ Controller
View ↛ Controller
8. 🔄 扩展性示例
8.1 添加新功能 - 乘2功能
8.1.1 修改 Model
c
// model.h 添加
void model_multiply_by_two(Model *m);
// model.c 实现
void model_multiply_by_two(Model *m) {
int new_value = m->counter * 2;
if (new_value <= m->max_value) {
m->counter = new_value;
printf("[Model] 值乘2: %d\n", m->counter);
} else {
printf("[Model] 乘2会超出最大值: %d\n", m->max_value);
}
}
8.1.2 修改 Controller
c
// controller.h 添加事件
typedef enum {
// ... 原有事件
EVENT_MULTIPLY_2,
} Event;
// controller.c 添加处理
case EVENT_MULTIPLY_2:
model_multiply_by_two(&g_model);
break;
// 修改输入处理
static Event get_user_input(void) {
int ch = getchar();
switch (ch) {
// ... 原有case
case '*':
case 'x':
return EVENT_MULTIPLY_2;
}
}
8.1.3 修改 View
c
// view.c 更新帮助信息
printf(" [+] 增加 [-] 减少 [*] 乘2 [r] 重置 [q] 退出\n");
9. 📊 性能考虑
9.1 嵌入式环境优化建议
c
// 1. 避免不必要的刷新
static int last_value = 0;
void view_display_optimized(Model *m) {
if (m->counter != last_value) { // 只在值变化时刷新
last_value = m->counter;
printf("\r当前值: %d ", m->counter);
fflush(stdout);
}
}
// 2. 使用静态缓冲区减少内存分配
static char display_buffer[64];
void view_format(Model *m) {
snprintf(display_buffer, sizeof(display_buffer),
"Value: %d", m->counter);
// 然后一次性输出
}
// 3. 事件驱动代替轮询
// 使用中断或回调,而不是不断轮询输入
10. 🧪 单元测试示例
c
// test_model.c
#include "model.h"
#include <assert.h>
void test_model_increment() {
Model m;
model_init(&m);
assert(model_get_value(&m) == 0);
model_increment(&m);
assert(model_get_value(&m) == 1);
// 测试边界
for(int i = 0; i < 20; i++) {
model_increment(&m);
}
assert(model_get_value(&m) == 10); // 不应超过最大值
}
int main() {
test_model_increment();
printf("所有测试通过!\n");
return 0;
}
11. 📝 总结
这个最小化MVC示例展示了:
- 清晰的职责分离:每个组件职责明确
- 低耦合:组件间通过接口交互
- 高内聚:相关功能集中在一起
- 易扩展:添加新功能只需修改少量代码
- 可测试:Model可以独立测试
适用场景:
- 嵌入式GUI应用
- 人机交互界面
- 需要频繁修改界面的项目
- 多人协作开发的项目
这个模式特别适合嵌入式系统,因为它让代码结构清晰,便于维护和测试,同时保持了代码的简洁性。