MVVM设计模式
MVVM(Model-View-ViewModel)是一种软件设计模式,MVVM模式由三个主要部分组成:
Model(模型):负责管理应用程序的业务逻辑和数据。它不关心UI如何展示数据,主要负责与服务器通信和数据处处理。
View(视图):直接与用户交互的界面,负责展示数据。视图不包含业务逻辑,只负责数据的展示。
ViewModel(视图模型):作为视图和模型之间的桥梁,包含UI逻辑,通过数据绑定机制驱动视图的变化。它处理用户的输入并将结果传递给模型。
MVVM的核心机制和优势
数据绑定是MVVM的核心机制,它允许视图和视图模型之间的自动同步,无需手动编写繁琐的代码来更新界面。
这种设计模式的主要优势包括:
解耦:视图和模型完全解耦,视图模型管理两者的交互,提高了代码的可维护性和扩展性。
可测试性:由于视图模型不依赖于视图,可以通过单元测试来验证业务逻辑的正确性,而不需要启动UI环境。
提高开发效率:通过数据绑定和事件处理,简化了开发过程,减少了手动同步数据的需要。
项目结构
mvvm_example/
├── main.cpp
├── view.h
├── view.cpp
├── viewmodel.h
├── viewmodel.cpp
├── model.h
└── model.cpp
代码实现
model.h
#ifndef MODEL_H
#define MODEL_H#include <QObject>
#include <QString>class Model : public QObject
{Q_OBJECTQ_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged)public:explicit Model(QObject *parent = nullptr);QString data() const;void setData(const QString &newData);signals:void dataChanged();private:QString m_data;
};#endif // MODEL_H
model.cpp
#include "model.h"Model::Model(QObject *parent) : QObject(parent)
{
}QString Model::data() const
{return m_data;
}void Model::setData(const QString &newData)
{if (m_data != newData) {m_data = newData;emit dataChanged();}
}
viewmodel.h
#ifndef VIEWMODEL_H
#define VIEWMODEL_H#include <QObject>
#include "model.h"class ViewModel : public QObject
{Q_OBJECTQ_PROPERTY(QString displayData READ displayData NOTIFY displayDataChanged)public:explicit ViewModel(Model *model, QObject *parent = nullptr);QString displayData() const;signals:void displayDataChanged();public slots:void onModelDataChanged();void setViewModelData(const QString &newData);private:Model *m_model;
};#endif // VIEWMODEL_H
viewmodel.cpp
#include "viewmodel.h"ViewModel::ViewModel(Model *model, QObject *parent) : QObject(parent), m_model(model)
{connect(m_model, &Model::dataChanged, this, &ViewModel::onModelDataChanged);
}QString ViewModel::displayData() const
{return m_model->data();
}void ViewModel::onModelDataChanged()
{emit displayDataChanged();
}void ViewModel::setViewModelData(const QString &newData)
{m_model->setData(newData);
}
view.h
#ifndef VIEW_H
#define VIEW_H#include <QWidget>
#include "viewmodel.h"QT_BEGIN_NAMESPACE
namespace Ui { class View; }
QT_END_NAMESPACEclass View : public QWidget
{Q_OBJECTpublic:explicit View(ViewModel *viewModel, QWidget *parent = nullptr);~View();private slots:void on_pushButton_clicked();void updateLabel(const QString &data);private:Ui::View *ui;ViewModel *m_viewModel;
};
#endif // VIEW_H
view.cpp
#include "view.h"
#include "ui_view.h"View::View(ViewModel *viewModel, QWidget *parent): QWidget(parent), ui(new Ui::View), m_viewModel(viewModel)
{ui->setupUi(this);// 绑定 ViewModel 的 displayDataChanged 信号到更新标签的槽函数connect(m_viewModel, &ViewModel::displayDataChanged, this, [this]() {updateLabel(m_viewModel->displayData());});// 绑定按钮点击信号到槽函数connect(ui->pushButton, &QPushButton::clicked, this, &View::on_pushButton_clicked);
}View::~View()
{delete ui;
}void View::on_pushButton_clicked()
{QString input = ui->lineEdit->text();m_viewModel->setViewModelData(input);
}void View::updateLabel(const QString &data)
{ui->label->setText(data);
}
main.cpp
#include "view.h"
#include "model.h"
#include "viewmodel.h"
#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);Model model;ViewModel viewModel(&model);View view(&viewModel);view.show();return a.exec();
}
在上述代码里,MainWindow
类实际上就充当了 View
的角色。不过为了更清晰地体现 View
的概念,我们可以把界面相关的操作进一步封装,并且更明确地展示 View
、ViewModel
和 Model
之间的交互。以下是改进后的代码:
项目结构
mvvm_example/
├── main.cpp
├── view.h
├── view.cpp
├── viewmodel.h
├── viewmodel.cpp
├── model.h
└── model.cpp
代码实现
model.h
#ifndef MODEL_H
#define MODEL_H#include <QObject>
#include <QString>class Model : public QObject
{Q_OBJECTQ_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged)public:explicit Model(QObject *parent = nullptr);QString data() const;void setData(const QString &newData);signals:void dataChanged();private:QString m_data;
};#endif // MODEL_H
model.cpp
#include "model.h"Model::Model(QObject *parent) : QObject(parent)
{
}QString Model::data() const
{return m_data;
}void Model::setData(const QString &newData)
{if (m_data != newData) {m_data = newData;emit dataChanged();}
}
viewmodel.h
#ifndef VIEWMODEL_H
#define VIEWMODEL_H#include <QObject>
#include "model.h"class ViewModel : public QObject
{Q_OBJECTQ_PROPERTY(QString displayData READ displayData NOTIFY displayDataChanged)public:explicit ViewModel(Model *model, QObject *parent = nullptr);QString displayData() const;signals:void displayDataChanged();public slots:void onModelDataChanged();void setViewModelData(const QString &newData);private:Model *m_model;
};#endif // VIEWMODEL_H
viewmodel.cpp
#include "viewmodel.h"ViewModel::ViewModel(Model *model, QObject *parent) : QObject(parent), m_model(model)
{connect(m_model, &Model::dataChanged, this, &ViewModel::onModelDataChanged);
}QString ViewModel::displayData() const
{return m_model->data();
}void ViewModel::onModelDataChanged()
{emit displayDataChanged();
}void ViewModel::setViewModelData(const QString &newData)
{m_model->setData(newData);
}
view.h
#ifndef VIEW_H
#define VIEW_H#include <QWidget>
#include "viewmodel.h"QT_BEGIN_NAMESPACE
namespace Ui { class View; }
QT_END_NAMESPACEclass View : public QWidget
{Q_OBJECTpublic:explicit View(ViewModel *viewModel, QWidget *parent = nullptr);~View();private slots:void on_pushButton_clicked();void updateLabel(const QString &data);private:Ui::View *ui;ViewModel *m_viewModel;
};
#endif // VIEW_H
view.cpp
#include "view.h"
#include "ui_view.h"View::View(ViewModel *viewModel, QWidget *parent): QWidget(parent), ui(new Ui::View), m_viewModel(viewModel)
{ui->setupUi(this);// 绑定 ViewModel 的 displayDataChanged 信号到更新标签的槽函数connect(m_viewModel, &ViewModel::displayDataChanged, this, [this]() {updateLabel(m_viewModel->displayData());});// 绑定按钮点击信号到槽函数connect(ui->pushButton, &QPushButton::clicked, this, &View::on_pushButton_clicked);
}View::~View()
{delete ui;
}void View::on_pushButton_clicked()
{QString input = ui->lineEdit->text();m_viewModel->setViewModelData(input);
}void View::updateLabel(const QString &data)
{ui->label->setText(data);
}
main.cpp
#include "view.h"
#include "model.h"
#include "viewmodel.h"
#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);Model model;ViewModel viewModel(&model);View view(&viewModel);view.show();return a.exec();
}
代码解释
View
类
- 构造函数:接收一个
ViewModel
对象的指针,负责初始化界面并建立信号与槽的连接。 on_pushButton_clicked
槽函数:当用户点击按钮时,获取输入框中的文本,然后调用ViewModel
的setViewModelData
方法更新数据。updateLabel
槽函数:当ViewModel
的displayData
发生变化时,更新界面上的标签文本。
ViewModel
类
- 作为
View
和Model
之间的桥梁,负责处理业务逻辑和数据转换。当Model
的数据发生变化时,会发出displayDataChanged
信号通知View
更新。
Model
类
- 负责存储和管理数据,当数据发生变化时发出
dataChanged
信号。
main.cpp
- 创建
Model
、ViewModel
和View
对象,并显示View
。
通过这种方式,我们清晰地划分了 View
、ViewModel
和 Model
的职责,实现了 MVVM 模式。
相关文章:
MVVM设计模式
MVVM(Model-View-ViewModel)是一种软件设计模式,MVVM模式由三个主要部分组成: Model(模型):负责管理应用程序的业务逻辑和数据。它不关心UI如何展示数据,主要负责与服务器通信和数据处处…...
解决:Cannot find a valid baseurl for repo: base/7/x86_64
传送门 repo_file/etc/yum.repos.d/CentOS-Base.repo cp ${repo_file} ~/CentOS-Base.repo.backup sudo sed -i s/#baseurl/baseurl/ ${repo_file} sudo sed -i s/mirrorlist.centos.org/vault.centos.org/ ${repo_file} sudo sed -i s/mirror.centos.org/vault.centos.org/ $…...
ffmpeg -codecs
1. ffmpeg -codecs -loglevel quiet 显示ffmpeg支持的编解码器 2. 输出 选取部分结果: Codecs: D..... Decoding supported .E.... Encoding supported ..V... Video codec ..A... Audio codec ..S... Subtitle codec ...I.. Intra frame-only code…...

社区版IDEA中配置TomCat(详细版)
文章目录 1、下载Smart TomCat2、配置TomCat3、运行代码 1、下载Smart TomCat 由于小编的是社区版,没有自带的tomcat server,所以在设置的插件里面搜索,安装第一个(注意:安装时一定要关闭外网,小编因为这个…...
强化学习 DPO 算法:基于人类偏好,颠覆 PPO 传统策略
目录 一、引言二、强化学习基础回顾(一)策略(二)价值函数 三、近端策略优化(PPO)算法(一)算法原理(二)PPO 目标函数(三)代码示例&…...
长安链支撑全国不动产登记数据可信流通
转自人民日报客户端 不动产登记事关亿万企业、家庭的切身利益。促进不动产登记数据安全流通、业务高效协同,是各方持续努力的目标。记者1月7日从国家区块链技术创新中心获悉,我国自主可控、性能领先的区块链软硬件技术体系长安链,支撑自然资…...

GitCode 助力 Dora SSR:开启游戏开发新征程
项目仓库(点击阅读原文链接可直达) https://gitcode.com/ippclub/Dora-SSR 跨越技术藩篱,构建游戏开发乐园 Dora SSR 是一款致力于打破游戏开发技术壁垒的开源游戏引擎。其诞生源于开发者对简化跨平台游戏开发环境搭建的强烈渴望࿰…...
获取 Windows 视频时长的正确方式——Windows Shell API 深度解析
在 Qt 开发中,有时需要获取视频文件的时长,最直接的方法是在 Windows 上使用 Windows Shell API。然而,这涉及到 IShellItem、IPropertyStore 等 COM 组件,并需要正确处理 PKEY_Media_Duration。本篇文章将详细解析 Windows Shell API 获取视频时长的正确实现方式,并解决常…...
Linux系统安装Nginx详解(适用于CentOS 7)
目录 1. 更新系统包 2. 安装EPEL仓库 3. 安装Nginx 4. 启动Nginx服务 5. 设置Nginx开机自启 6. 检查Nginx状态 7. 配置防火墙 8. 访问Nginx默认页面 9. 配置Nginx(可选) 10. 重启Nginx 解决步骤 1. 检查系统版本 2. 移除错误的 Nginx 仓库 …...

深入理解Java对接DeepSeek
其实,整个对接过程很简单,就四步,获取key,找到接口文档,接口测试,代码对接。 1.获取 KEY https://platform.deepseek.com/transactions 直接付款就是了(现在官网暂停充值2025年2月7日…...
flutter isolate到底是啥
在 Flutter 中,Isolate 是一种实现多线程编程的机制,下面从概念、工作原理、使用场景、使用示例几个方面详细介绍: 概念 在 Dart 语言(Flutter 开发使用的编程语言)里,每个 Dart 程序至少运行在一个 Isol…...
深入剖析 Apache Shiro550 反序列化漏洞及复现
目录 前言 一、认识 Apache Shiro 二、反序列化漏洞:隐藏在数据转换中的风险 三、Shiro550 漏洞:会话管理中的致命缺陷 四、漏洞危害:如多米诺骨牌般的连锁反应 五、漏洞复现:揭开攻击的神秘面纱 (一࿰…...
计算机毕业设计——Springboot的简历系统
📘 博主小档案: 花花,一名来自世界500强的资深程序猿,毕业于国内知名985高校。 🔧 技术专长: 花花在深度学习任务中展现出卓越的能力,包括但不限于java、python等技术。近年来,花花更…...

【kubernetes组件合集】深入解析Kubernetes组件之三:client-go
深入解析Kubernetes组件之三:client-go 目录 深入解析Kubernetes组件之三:client-go 引言 1. client-go简介 2. client-go的功能 2.1 资源操作 2.2 资源监听 2.3 认证和授权 2.4 错误处理和重试 2.5 扩展性和定制化 3. 使用client-go与Kubern…...

线程池-抢票系统性能优化
文章目录 引言-购票系统线程池购票系统-线程池优化 池化 vs 未池化 引言-购票系统 public class App implements Runnable {private static int tickets 100;private static int users 10000;private final ReentrantLock lock new ReentrantLock(true);public void run() …...
WebSocket 握手过程
文章目录 1. WebSocket 握手过程概述2. 客户端发送握手请求3. 服务器响应握手请求4. 客户端验证握手响应5. 建立 WebSocket 连接6. 安全性与注意事项7. 应用示例 在现代 Web 开发中,WebSocket 协议因其高效的实时通信能力而被广泛应用。WebSocket 允许客户端和服务器…...

VMware 虚拟机 ubuntu 20.04 扩容工作硬盘
一、关闭虚拟机 关闭虚拟机参考下图,在vmware 调整磁盘容量 二、借助工具fdisk testubuntu ~ $ df -h Filesystem Size Used Avail Use% Mounted on udev 1.9G 0 1.9G 0% /dev tmpfs 388M 3.1M 385M 1% /run /dev/sda5 …...

备战蓝桥杯:二分算法之牛可乐和魔法封印问题
这道题就是一道简单的模板题,我们分析一下!,首先我们要找大于等于x的起始位置 我们还是用两个指针,left指向1,right指向n,如果a[mid]<x left mid1 如果a[mid]>x 就让right mid 如果数组全是小于x…...
普通用户授权docker使用权限
1、检查docker用户组 sudo cat /etc/group |grep docker 若显示:docker:x:999: # 表示存在否则创建docker用户组: sudo groupadd docker2、查看 /var/run/docker.sock 的属性 ll /var/run/docker.sock 显示: srw-rw---- 1 root root 0 1月…...

【实战篇】DeepSeek + ElevenLabs:让人工智能“开口说话”,打造你的专属语音助手!
最近,AI语音合成技术真是火得不行,各种“开口脆”的AI声音层出不穷,听得我直呼“这也太像真人了吧!” 作为一个科技爱好者,我当然不能错过这股潮流,这不,最近就沉迷于用 DeepSeek 和 ElevenLabs 这两款神器,捣鼓各种人声音频,简直停不下来! 先来科普一下这两位“主角…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
【位运算】消失的两个数字(hard)
消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...