设计模式 | MVC模式
MVC(Model-View-Controller)是软件工程中最经典的架构模式之一,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种分离实现了关注点分离,使应用程序更易于维护、扩展和测试。本文将全面解析MVC模式的核心概念、实现方法以及在C++中的高效实践。
为什么需要MVC模式?
在用户界面开发中,常见痛点包括:
-
业务逻辑与UI耦合:界面变化影响业务逻辑
-
代码复用困难:相同数据在不同视图难以复用
-
测试复杂度高:UI依赖导致核心逻辑难以测试
-
团队协作冲突:UI设计师和开发人员工作相互干扰
-
维护成本高:简单修改需要全面回归测试
MVC通过职责分离解决这些问题:
-
模型:管理数据和业务规则
-
视图:处理数据显示
-
控制器:处理用户输入和协调
MVC模式的核心概念
架构关系图解
用户
↓
[控制器] ← 用户输入
↓ ↓
[更新模型] [更新视图]
↓
[模型] → 数据变化通知 → [视图]
核心组件职责
-
模型(Model)
-
封装应用程序状态和核心功能
-
包含数据操作和业务逻辑
-
提供状态变化通知机制
-
独立于UI实现
-
-
视图(View)
-
可视化展示模型数据
-
订阅模型变更通知
-
包含最小化的展示逻辑
-
支持多个视图展示相同数据
-
-
控制器(Controller)
-
处理用户输入(点击、键盘等)
-
调用模型方法更新状态
-
选择适当视图响应
-
协调模型和视图交互
-
C++实现:股票监控系统
完整实现一个支持实时数据更新的股票监控系统:
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <ctime>
#include <iomanip>
#include <algorithm>
#include <cmath>
#include <random>
#include <chrono>
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
#include <list>
// ================= 模型:股票数据 =================
class StockModel {
public:
StockModel(const std::string& symbol, double price)
: symbol_(symbol), price_(price), lastUpdate_(std::time(nullptr)) {}
// 获取当前价格
double getPrice() const {
std::lock_guard lock(mutex_);
return price_;
}
// 获取股票代码
const std::string& getSymbol() const {
return symbol_;
}
// 获取最后更新时间
std::string getLastUpdate() const {
char buffer[80];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&lastUpdate_));
return buffer;
}
// 更新价格
void updatePrice(double newPrice) {
{
std::lock_guard lock(mutex_);
price_ = newPrice;
lastUpdate_ = std::time(nullptr);
}
notifyObservers();
}
// 注册观察者(视图)
void registerObserver(std::function<void()> observer) {
std::lock_guard lock(mutex_);
observers_.push_back(observer);
}
// 获取历史价格
const std::vector<double>& getHistory() const {
return history_;
}
// 添加历史记录
void addHistoryRecord(double price) {
history_.push_back(price);
// 保持最近100条记录
if (history_.size() > 100) {
history_.erase(history_.begin());
}
}
private:
// 通知所有观察者
void notifyObservers() {
std::lock_guard lock(mutex_);
for (auto& observer : observers_) {
observer();
}
}
mutable std::mutex mutex_;
std::string symbol_;
double price_;
std::time_t lastUpdate_;
std::vector<double> history_;
std::list<std::function<void()>> observers_;
};
// ================= 视图基类 =================
class StockView {
public:
virtual ~StockView() = default;
virtual void update() = 0;
virtual void display() const = 0;
virtual std::string getViewName() const = 0;
};
// ================= 具体视图:表格视图 =================
class TableView : public StockView {
public:
explicit TableView(StockModel& model) : model_(model) {
model_.registerObserver([this] { update(); });
}
void update() override {
// 不需要立即刷新,显示时更新
}
void display() const override {
std::cout << "\n===== 股票表格视图 =====\n";
std::cout << "代码: " << model_.getSymbol() << "\n";
std::cout << "价格: ¥" << std::fixed << std::setprecision(2) << model_.getPrice() << "\n";
std::cout << "更新时间: " << model_.getLastUpdate() << "\n";
std::cout << "========================\n";
}
std::string getViewName() const override {
return "表格视图";
}
private:
StockModel& model_;
};
// ================= 具体视图:图表视图 =================
class ChartView : public StockView {
public:
explicit ChartView(StockModel& model) : model_(model) {
model_.registerObserver([this] { update(); });
}
void update() override {
// 不需要立即刷新,显示时更新
}
void display() const override {
const auto& history = model_.getHistory();
if (history.empty()) return;
std::cout << "\n===== 股票价格图表 =====\n";
std::cout << "代码: " << model_.getSymbol() << "\n";
// 计算图表范围
double maxPrice = *std::max_element(history.begin(), history.end());
double minPrice = *std::min_element(history.begin(), history.end());
double range = maxPrice - minPrice;
if (range < 0.01) range = 1.0; // 避免除以零
const int chartHeight = 10;
const int chartWidth = std::min(50, static_cast<int>(history.size()));
// 绘制图表
for (int y = chartHeight; y >= 0; y--) {
double priceLevel = minPrice + range * (static_cast<double>(y) / chartHeight);
std::cout << std::setw(8) << std::fixed << std::setprecision(2) << priceLevel << " | ";
for (int x = 0; x < chartWidth; x++) {
int idx = history.size() - chartWidth + x;
if (idx < 0) continue;
double price = history[idx];
double pos = (price - minPrice) / range * chartHeight;
if (std::abs(pos - y) < 0.5) {
std::cout << "●"; // 数据点
} else if (y == 0 || y == chartHeight) {
std::cout << "─"; // 横轴
} else {
std::cout << " ";
}
}
std::cout << "\n";
}
// 时间轴标签
std::cout << " ";
for (int i = 0; i < chartWidth; i += 10) {
std::cout << "|" << std::setw(9);
}
std::cout << "\n 最新";
for (int i = 10; i <= chartWidth; i += 10) {
std::cout << std::setw(10) << i << "分钟前";
}
std::cout << "\n";
std::cout << "========================\n";
}
std::string getViewName() const override {
return "图表视图";
}
private:
StockModel& model_;
};
// ================= 控制器:股票控制器 =================
class StockController {
public:
StockController(StockModel& model, std::vector<std::unique_ptr<StockView>>& views)
: model_(model), views_(views), running_(false) {}
// 处理用户命令
void handleCommand(const std::string& command) {
if (command == "refresh") {
refreshViews();
}
else if (command == "views") {
listViews();
}
else if (command == "switch") {
switchView();
}
else if (command == "exit") {
stopSimulation();
}
else if (command == "help") {
showHelp();
}
else if (command.compare(0, 5, "price") == 0) {
try {
double price = std::stod(command.substr(6));
model_.updatePrice(price);
model_.addHistoryRecord(price);
} catch (...) {
std::cerr << "无效价格格式\n";
}
}
else {
std::cerr << "未知命令。输入 'help' 查看帮助。\n";
}
}
// 启动价格模拟
void startSimulation() {
if (running_) return;
running_ = true;
simulationThread_ = std::thread([this] {
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<> dist(0.0, 0.5); // 正态分布波动
double currentPrice = model_.getPrice();
while (running_) {
// 随机波动
double change = dist(gen);
currentPrice += currentPrice * change / 100.0;
// 确保价格不为负
if (currentPrice < 0.1) currentPrice = 0.1;
// 更新模型
model_.updatePrice(currentPrice);
model_.addHistoryRecord(currentPrice);
// 暂停
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
// 停止价格模拟
void stopSimulation() {
running_ = false;
if (simulationThread_.joinable()) {
simulationThread_.join();
}
}
// 刷新所有视图
void refreshViews() {
for (auto& view : views_) {
view->update();
view->display();
}
}
private:
// 列出所有可用视图
void listViews() {
std::cout << "\n可用视图:\n";
for (size_t i = 0; i < views_.size(); ++i) {
std::cout << i + 1 << ". " << views_[i]->getViewName() << "\n";
}
}
// 切换当前视图
void switchView() {
listViews();
std::cout << "选择视图编号: ";
int choice;
if (std::cin >> choice && choice > 0 && choice <= static_cast<int>(views_.size())) {
currentView_ = choice - 1;
views_[currentView_]->update();
views_[currentView_]->display();
} else {
std::cerr << "无效选择\n";
}
std::cin.ignore(); // 清除输入缓冲区
}
// 显示帮助信息
void showHelp() {
std::cout << "\n命令列表:\n"
<< " refresh - 刷新视图\n"
<< " views - 列出可用视图\n"
<< " switch - 切换当前视图\n"
<< " price [值] - 设置股票价格\n"
<< " exit - 退出程序\n"
<< " help - 显示此帮助\n";
}
StockModel& model_;
std::vector<std::unique_ptr<StockView>>& views_;
size_t currentView_ = 0;
std::atomic<bool> running_;
std::thread simulationThread_;
};
// ================= 用户界面 =================
class UserInterface {
public:
void run() {
// 创建模型
StockModel apple("AAPL", 150.0);
apple.addHistoryRecord(150.0);
// 创建视图
std::vector<std::unique_ptr<StockView>> views;
views.push_back(std::make_unique<TableView>(apple));
views.push_back(std::make_unique<ChartView>(apple));
// 创建控制器
StockController controller(apple, views);
// 启动价格模拟
controller.startSimulation();
// 显示初始视图
views[0]->display();
// 主命令循环
std::string command;
while (true) {
std::cout << "\n> ";
std::getline(std::cin, command);
if (command == "exit") {
controller.stopSimulation();
break;
}
controller.handleCommand(command);
}
}
};
// ================= 客户端代码 =================
int main() {
UserInterface ui;
ui.run();
return 0;
}
MVC模式的五大优势
1. 关注点分离
// 模型:仅关注数据
class UserModel {
void updateProfile(const ProfileData& data) {
// 验证和业务逻辑
}
};
// 视图:仅关注显示
class ProfileView {
void display(const UserModel& user) {
// 渲染用户界面
}
};
// 控制器:协调交互
class ProfileController {
void onSaveClicked() {
// 获取视图数据
// 调用模型更新
// 刷新视图
}
};
2. 多视图支持
// 同一个模型
StockModel stock("MSFT", 300.0);
// 不同视图
TableView view1(stock); // 表格视图
ChartView view2(stock); // 图表视图
MobileView view3(stock); // 移动端视图
3. 独立测试
// 测试模型(无UI依赖)
TEST(StockModelTest, PriceUpdate) {
StockModel model("TEST", 100.0);
model.updatePrice(105.0);
ASSERT_EQ(105.0, model.getPrice());
}
// 测试控制器(使用模拟视图)
TEST(StockControllerTest, RefreshCommand) {
MockView mockView;
StockController controller(model, mockView);
controller.handleCommand("refresh");
ASSERT_TRUE(mockView.refreshCalled);
}
4. 团队分工
-
模型团队:领域专家开发业务逻辑
-
视图团队:UI设计师实现用户界面
-
控制器团队:应用开发者处理交互逻辑
5. 技术栈灵活性
// 模型层使用C++实现高性能计算
class FinancialModel {
// 复杂的金融计算
};
// 视图层使用HTML/JS实现
class WebView {
// 基于Web的界面
};
// 控制器层使用REST API桥接
class RESTController {
// 处理HTTP请求
};
MVC模式的高级应用
1. 被动MVC与主动MVC
被动MVC:
// 控制器显式更新视图
void onDataChanged() {
controller->updateView();
}
主动MVC(观察者模式):
// 模型变更时自动通知视图
class ObservableModel {
void addObserver(Observer* obs) {
observers_.push_back(obs);
}
void notifyObservers() {
for (auto obs : observers_) obs->update();
}
};
2. MVC变体:MVP和MVVM
MVP (Model-View-Presenter):
// Presenter取代Controller
class StockPresenter {
void bindView(IStockView* view) {
view_ = view;
}
void updateModel(double price) {
model_.setPrice(price);
view_->showPrice(price);
}
};
MVVM (Model-View-ViewModel):
// ViewModel自动同步视图
class StockViewModel : public Observable {
Property<double> price { 0.0 };
};
// 视图绑定
<Label text="{Binding price}"/>
3. 前端控制器模式
class FrontController {
void handleRequest(Request& req) {
if (req.path == "/stocks") return StockController().handle(req);
if (req.path == "/profile") return ProfileController().handle(req);
// ...
}
};
MVC模式的应用场景
1. 桌面应用程序
// 文档编辑器
class DocumentModel { /* 文本内容 */ };
class EditorView { /* 文本渲染 */ };
class EditorController {
void onKeyPress(KeyEvent e) {
model_.insertText(e.key);
view_.refresh();
}
};
2. Web应用程序
// 电子商务系统
class ProductModel { /* 产品数据 */ };
class ProductView { /* HTML模板 */ };
class ProductController {
HttpResponse showProduct(int id) {
auto product = model_.getProduct(id);
return view_.render(product);
}
};
3. 游戏开发
// 角色系统
class CharacterModel { /* 角色状态 */ };
class CharacterView { /* 3D模型渲染 */ };
class CharacterController {
void handleInput(InputState input) {
model_.move(input.direction);
view_.updatePosition(model_.position());
}
};
MVC模式与其他模式的关系
| 模式 | 关系 | 区别 |
|---|---|---|
| 观察者 | MVC常使用观察者模式通知视图 | 观察者是通信机制,MVC是架构 |
| 策略 | 控制器可视为视图的策略 | 策略封装算法,控制器处理输入 |
| 组合 | 复杂视图使用组合模式 | 组合管理UI结构,视图负责显示 |
| 装饰器 | 动态增强视图功能 | 装饰器扩展对象,视图是显示层 |
MVC模式的挑战与解决方案
| 挑战 | 解决方案 |
|---|---|
| 控制器过于臃肿 | 使用前端控制器或命令模式 |
| 视图直接访问模型 | 通过控制器中介或使用DTO |
| 模型变更波及视图 | 使用观察者模式解耦 |
| 复杂视图状态管理 | 引入Presenter或ViewModel |
总结
MVC是用户界面架构的黄金标准,它通过:
-
清晰分层:模型、视图、控制器各司其职
-
解耦设计:业务逻辑与UI分离
-
灵活扩展:支持多视图、多平台
-
高效协作:团队并行开发
适用场景:
-
任何具有用户界面的应用程序
-
需要多种方式展示数据的系统
-
长期维护的大型项目
-
需要严格测试的业务逻辑
"MVC不是框架,而是思考用户界面架构的方式。它是分离关注点的经典实践。" - Martin Fowler
更多推荐



所有评论(0)