MVP 架构详解
MVP 架构将传统的 MVC 模式进一步改进,主要目的是让视图(View)尽量“被动”,所有业务逻辑都放在 Presenter 中,从而提高代码的测试性和可维护性。其三个核心组件说明如下:
Model(模型)
Model 负责应用的业务逻辑和数据处理。在本示例中,Model 提供一个接口用于计算字符串的长度。Model 与用户界面完全无关,仅关注数据和业务规则。View(视图)
View 负责构建用户界面,展示数据,并将用户的操作事件(例如按钮点击)传递给 Presenter。为了降低耦合,通常会定义一个 View 接口(例如 IView),而具体的 View(例如 MainWindow)实现这个接口。View 只负责展示,不直接包含业务逻辑。Presenter(表示层/中介者)
Presenter 作为中介者连接 Model 与 View。它接收来自 View 的用户操作,调用 Model 进行数据处理,再将处理结果反馈给 View 更新显示。Presenter 中的逻辑通常是纯 C++ 类,不依赖于具体的 GUI 组件,因此便于单元测试。
这种架构的优点在于:
关注点分离:业务逻辑与界面展示完全解耦。
提高可测试性:Presenter 逻辑可以独立测试,View 尽可能简单。
便于维护和扩展:每一层职责明确,修改其中一层对其它层影响较小。
示例
示例功能
构建一个简单的应用,界面包含:
- QLineEdit:供用户输入字符串;
- QPushButton:当用户点击按钮时,计算 QLineEdit 中文本的字符数;
- QLabel:显示计算后的字符数。
整体流程:
- 用户在 QLineEdit 中输入字符串;
- 点击按钮,触发控制器;
- 控制器调用模型计算字符串长度;
- 控制器将计算结果更新到 QLabel 显示出来。
示例代码
下面给出各个组件的示例代码,代码按照 MVP 的思想分为四个部分:
- 定义 View 接口
首先定义一个纯虚接口,规定 View 应实现的更新显示接口。
// IView.h
#ifndef IVIEW_H
#define IVIEW_H
#include <QString>
class IView
{
public:
virtual ~IView() {}
// 更新显示结果,例如将计算的长度写入 QLabel
virtual void updateResult(const QString &result) = 0;
};
#endif // IVIEW_H
- 定义 Model 类
Model 只负责业务逻辑,这里只提供计算字符串长度的方法
// Model.h
#ifndef MODEL_H
#define MODEL_H
#include <QString>
class Model
{
public:
Model() {}
// 计算并返回字符串的长度
int getTextLength(const QString &text) {
return text.length();
}
};
#endif // MODEL_H
- 定义 Presenter 类
Presenter 负责接收 View 传来的请求,调用 Model 处理,再通知 View 更新显示。
// Presenter.h
#ifndef PRESENTER_H
#define PRESENTER_H
#include "IView.h"
#include "Model.h"
#include <QString>
class Presenter
{
public:
Presenter(IView *view, Model *model)
: m_view(view), m_model(model) {}
// 处理按钮点击事件,计算文本长度并通知 View 更新显示
void onCalculateButtonClicked(const QString &inputText) {
int length = m_model->getTextLength(inputText);
m_view->updateResult("长度:" + QString::number(length));
}
private:
IView *m_view;
Model *m_model;
};
#endif // PRESENTER_H
- 实现具体 View(MainWindow)
MainWindow 继承自 QWidget 并实现 IView 接口,构建界面,同时将用户操作(按钮点击)转发给 Presenter。
// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QWidget>
#include "IView.h"
class QLineEdit;
class QPushButton;
class QLabel;
class Presenter;
class Model;
class MainWindow : public QWidget, public IView
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
// IView 接口实现:更新 QLabel 显示计算结果
void updateResult(const QString &result) override;
private slots:
// 当按钮点击时,调用 Presenter 处理
void handleCalculateButton();
private:
QLineEdit *m_lineEdit;
QPushButton *m_button;
QLabel *m_label;
// MVP 的其它组件
Presenter *m_presenter;
Model *m_model;
};
#endif // MAINWINDOW_H
// MainWindow.cpp
#include "MainWindow.h"
#include "Presenter.h"
#include "Model.h"
#include <QLineEdit>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
// 创建控件
m_lineEdit = new QLineEdit(this);
m_button = new QPushButton("计算长度", this);
m_label = new QLabel("结果显示", this);
// 布局管理
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(m_lineEdit);
layout->addWidget(m_button);
layout->addWidget(m_label);
setLayout(layout);
// 创建 Model 与 Presenter
m_model = new Model();
m_presenter = new Presenter(this, m_model);
// 连接按钮点击信号到槽函数
connect(m_button, &QPushButton::clicked, this, &MainWindow::handleCalculateButton);
}
MainWindow::~MainWindow()
{
delete m_presenter;
delete m_model;
}
void MainWindow::updateResult(const QString &result)
{
// 更新 QLabel 显示计算结果
m_label->setText(result);
}
void MainWindow::handleCalculateButton()
{
// 获取用户输入
QString inputText = m_lineEdit->text();
// 将输入传递给 Presenter 处理
m_presenter->onCalculateButtonClicked(inputText);
}
- 主函数入口
在 main 函数中创建 QApplication 对象并显示 MainWindow。
// main.cpp
#include <QApplication>
#include "MainWindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.setWindowTitle("Qt MVP 示例");
window.resize(300, 200);
window.show();
return app.exec();
}
总结
在上述 MVP 示例中,我们将应用程序分为三个部分:
Model 负责计算字符串的长度,不依赖于任何界面;
View(MainWindow)构建用户界面,展示 QLineEdit、按钮和 QLabel,并实现 IView 接口,用于接收 Presenter 的更新通知;
Presenter 接收 View 的操作请求,调用 Model 计算结果,再将结果传递回 View 更新界面。
这种设计使得业务逻辑与界面展示彻底分离,便于单元测试 Presenter 以及在项目中对各模块进行独立开发和维护。