界面架构- MVP(Qt)

发布于:2025-04-04 ⋅ 阅读:(22) ⋅ 点赞:(0)

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:显示计算后的字符数。

整体流程:

  1. 用户在 QLineEdit 中输入字符串;
  2. 点击按钮,触发控制器;
  3. 控制器调用模型计算字符串长度;
  4. 控制器将计算结果更新到 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 以及在项目中对各模块进行独立开发和维护。