深入理解MVP架构模式
引言
MVP(Model-View-Presenter,模型-视图-提供者)是一种广泛应用于软件开发中的架构模式,是经典MVC(Model-View-Controller)的变种。在传统的MVC模式中,Model和View之间存在直接的依赖和数据交互,这种设计在一定程度上会导致代码耦合性增加,影响系统的可维护性和可扩展性。而在MVP模式中,Model和View完全解耦,它们之间的交互通过Presenter来实现。这种设计提高了代码的可维护性和可测试性,降低了复杂度。
本文将深入探讨MVP的架构概念、体系结构,最后通过一个完整的登录功能示例,展示如何在实际项目中运用MVP架构。
架构概念
MVP架构的核心思想是通过引入一个Presenter作为中间层,解耦Model和View之间的直接依赖关系,使得View仅负责UI渲染和用户交互,而Presenter处理业务逻辑并与Model交互。在MVP模式下,Model和View不存在任何直接的依赖和交互。它们之间的通讯完全通过Presenter来实现,这样可以更好地分离职责,降低代码的耦合度,提高代码的清晰性和可测试性。
MVP模式的依赖和交互关系如下图所示。MVP中的各部分之间的通信都是双向的,Presenter作为中间者,居中协调View和Model的交互。

MVP架构的三层功能如下:
- Model:负责处理数据的获取、存储和管理,通常包含数据访问层(如数据库、API等)和实体模型
- View:负责展示UI并与用户交互。View是一个“被动视图”(Passive View),它不会包含任何业务逻辑,只接受Presenter的指令来更新界面。
- Presenter:是MVP的核心组件,负责处理业务逻辑。Presenter从Model获取数据,并将其传递给View,同时监听View的用户操作并处理相应的逻辑。
体系结构
三层解耦
MVP通过引入Presenter层,彻底实现了Model、View和Presenter三层的解耦:
- Model:专注于数据处理,不关心UI展示。
- View:专注于UI展示,不关心数据的来源和逻辑处理。
- Presenter:负责在View和Model之间传递信息和业务逻辑的处理。
与MVC相比,MVP最大的不同点在于View和Model之间没有直接交互,而是通过Presenter桥接。Presenter通过接口与View和Model通信,保证了各组件之间的低耦合性,从而使代码更易于维护和测试。
双向通信
在MVP中,Presenter是核心,View通过接口与Presenter通信,而Presenter又通过调用Model来处理数据,最后将处理结果返回给View。通信流程如下:
- 用户在View中触发某个操作(如点击按钮)。
- View将该操作通知给Presenter。
- Presenter根据业务逻辑决定如何处理事件,并通过Model获取数据。
- Model返回处理结果给Presenter。
- Presenter将结果传递给View,更新UI。
MVP的典型通信流程如下图所示:

被动视图(Passive View)
MVP中的View是“被动视图”,它只负责接收用户的输入和展示UI,而不会主动处理数据或业务逻辑。所有的逻辑都由Presenter处理,这样设计让View变得更加简单,并且使Presenter更具测试性。Presenter通过接口与View交互,使得Presenter可以独立于具体的UI实现进行单元测试。
测试友好
MVP模式的另一个显著优势是它非常利于单元测试。Presenter包含了所有的业务逻辑,且通过接口与View和Model交互,因此可以轻松地使用Mock对象来隔离实际的View和Model,从而更高效地进行测试。与MVC不同,MVP的View只是被动更新UI,没有复杂的逻辑需要测试,Presenter和Model的逻辑则可以通过自动化测试进行充分验证。
完整示例:登录功能
为了更好地理解MVP架构,接下来通过一个简单的登录功能示例来展示MVP模式在实践中的应用。
定义接口
首先,为了保证Presenter与View和Model的解耦,我们需要为View和Model定义接口。
LoginView接口:
public interface LoginView {void showLoginSuccess(String message);void showLoginError(String error);void showLoading();void hideLoading();
}
LoginModel接口:
public interface LoginModel {void login(String username, String password, LoginCallback callback);interface LoginCallback {void onSuccess(String message);void onError(String error);}
}
实现Model和Presenter
LoginModel实现:
public class LoginModelImpl implements LoginModel {@Overridepublic void login(String username, String password, LoginCallback callback) {// 模拟网络请求if ("admin".equals(username) && "1234".equals(password)) {callback.onSuccess("Login Successful");} else {callback.onError("Invalid Credentials");}}
}
LoginPresenter实现:
public class LoginPresenter {private LoginView loginView;private LoginModel loginModel;public LoginPresenter(LoginView view) {this.loginView = view;this.loginModel = new LoginModelImpl(); // 可以通过依赖注入解耦}public void login(String username, String password) {loginView.showLoading();loginModel.login(username, password, new LoginModel.LoginCallback() {@Overridepublic void onSuccess(String message) {loginView.hideLoading();loginView.showLoginSuccess(message);}@Overridepublic void onError(String error) {loginView.hideLoading();loginView.showLoginError(error);}});}
}
View层实现
最后,我们在View层(如Activity或Fragment)中实现LoginView接口,并将操作委托给Presenter。
LoginActivity实现:
public class LoginActivity extends AppCompatActivity implements LoginView {private EditText usernameEditText;private EditText passwordEditText;private Button loginButton;private ProgressBar progressBar;private LoginPresenter loginPresenter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);// 初始化UI组件usernameEditText = findViewById(R.id.username);passwordEditText = findViewById(R.id.password);loginButton = findViewById(R.id.loginButton);progressBar = findViewById(R.id.progressBar);// 初始化PresenterloginPresenter = new LoginPresenter(this);// 设置按钮点击事件loginButton.setOnClickListener(v -> {String username = usernameEditText.getText().toString();String password = passwordEditText.getText().toString();loginPresenter.login(username, password);});}@Overridepublic void showLoginSuccess(String message) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show();}@Overridepublic void showLoginError(String error) {Toast.makeText(this, error, Toast.LENGTH_SHORT).show();}@Overridepublic void showLoading() {progressBar.setVisibility(View.VISIBLE);}@Overridepublic void hideLoading() {progressBar.setVisibility(View.GONE);}
}
LoginActivity布局:
LoginActivity相对应activity_login.xml布局文件,它定义了简单的登录界面,包含用户名输入框、密码输入框、登录按钮和加载进度条。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"android:padding="16dp"><!-- 用户名输入框 --><EditTextandroid:id="@+id/username"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Username"android:inputType="text"android:padding="12dp"android:layout_marginBottom="16dp"/><!-- 密码输入框 --><EditTextandroid:id="@+id/password"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Password"android:inputType="textPassword"android:padding="12dp"android:layout_marginBottom="16dp"/><!-- 登录按钮 --><Buttonandroid:id="@+id/loginButton"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Login"android:padding="12dp"android:layout_marginBottom="16dp"/><!-- 加载进度条 --><ProgressBarandroid:id="@+id/progressBar"android:layout_width="wrap_content"android:layout_height="wrap_content"android:visibility="gone"/></LinearLayout>
布局说明:
- 用户名输入框 (EditText):用户输入登录用户名的地方,id为username。
- 密码输入框 (EditText):用户输入登录密码的地方,id为password,并设置了输入类型为密码格式。
- 登录按钮 (Button):用于触发登录操作的按钮,id为loginButton。
- 加载进度条 (ProgressBar):在处理登录请求时显示的加载指示器,默认隐藏(visibility=“gone”),只有在请求处理中显示。
结论
MVP架构通过将View、Model、Presenter三层彻底解耦,使得业务逻辑、UI展示、数据处理分别在不同的层中负责。这样不仅提高了代码的可维护性和可测试性,也使得项目的扩展性更强。在实际开发中,MVP非常适用于复杂度较高的应用程序,尤其是在需要严格分离UI逻辑和业务逻辑的场景下
相关文章:
深入理解MVP架构模式
引言 MVP(Model-View-Presenter,模型-视图-提供者)是一种广泛应用于软件开发中的架构模式,是经典MVC(Model-View-Controller)的变种。在传统的MVC模式中,Model和View之间存在直接的依赖和数据交…...
Java面试题七
一、Java中的集合框架是如何组织的?列举几个常用的集合类。 Java中的集合框架是一个设计用来存储和操作对象集合的统一架构。它主要由两大接口派生出来:Collection和Map。这两个接口及其子接口和实现类共同构成了Java集合框架的主体。 集合框架的组织结…...
linux网络编程3——http服务器的实现和性能测试
http服务器的实现 本文使用上一篇博文实现的epollreactor百万并发的服务器实现了一个使用http协议和WebSocket协议的WebServer。 完整代码请看我的github项目 1. 水平触发(Level Trigger)与边沿触发(Edge Trigger) 1.1 水平触发 水平触发是一种状态驱动机制。当文件描述符&a…...
Docker部署Kamailio,并使用LinPhone实现网络通话
前提条件 准备一个路由器,一个服务器,两个终端设备(手机或电脑) docker部署安装 我使用的是windows系统,docker desktop 先启动Docker desktop打开cmd,输入docker命令docker run --name kamailio --rm…...
JAVA-石头迷阵小游戏
采用企业式项目结构,接下来我将分享全部代码和结构,希望大家点点关注! 这是我的结构。首先使用IDE创建一个Module,命名stone-maze,接着把自带src下的main方法删除,接着在src下创建包,包名为com.wmuj,接着创建APP类代码如下: package com.wmuj;public class App {publ…...
鸿蒙--进度条通知
主要介绍如何使用通知能力和基础组件,实现模拟下载文件,发送通知的案例。 效果 代码结构 ├──entry/src/main/ets // 代码区 │ ├──common │ │ ├──constants │ │ │ └──CommonConstants.ets // 公共常量类 │ │ └──utils │ │ ├──Logger.ets //…...
搜维尔科技:varjo xr-4开箱测评,工业用途头显,一流视觉保真度
varjo xr-4开箱测评,工业用途头显,一流视觉保真度 搜维尔科技:varjo xr-4开箱测评,工业用途头显,一流视觉保真度...
mysql数据量分库分表
一、分库分表参考阈值 分库分表是解决大规模数据和高并发访问问题的常用策略。虽然没有绝对的阈值来决定何时进行分库分表,但以下是一些参考阈值和考虑因素,可以帮助你做出决策: 1.1 数据量阈值 单表数据行数:当单表的数据行数…...
Vite创建Vue3项目以及Vue3相关基础知识
1.创建Vue3项目 1.运行创建项目命令 # 使用 npm npm create vitelatest2、填写项目名称 3、选择前端框架 4、选择语法类型 5、按提示运行代码 不出意外的话,运行之后应该会出现 下边这个页面 6.延伸学习:对比webpack和vite(这个是面试必考…...
Elasticsearch封装公共索引增删改查
什么是索引? 定义:索引是 Elasticsearch 中用于存储数据的逻辑命名空间。它由多个文档组成,每个文档是一个 JSON 格式的结构化数据对应关系:在关系数据库中,索引类似于表;而在 Elasticsearch 中࿰…...
Python异常检测:Isolation Forest与局部异常因子(LOF)详解
这里写目录标题 Python异常检测:Isolation Forest与局部异常因子(LOF)详解引言一、异常检测的基本原理1.1 什么是异常检测?1.2 异常检测的应用场景 二、Isolation Forest2.1 Isolation Forest的原理2.1.1 算法步骤 2.2 Python实现…...
Git的原理和使用(二)
1. git的版本回退 之前我们也提到过,Git 能够管理⽂件的历史版本,这也是版本控制器重要的能⼒。如果有⼀天你发现 之前前的⼯作做的出现了很⼤的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本 回退的功能…...
docker 发布镜像
如果要推广自己的软件,势必要自己制作 image 文件。 1 制作自己的 Docker 容器 基于 centos 镜像构建自己的 centos 镜像,可以在 centos 镜像基础上,安装相关的软件,之后进行构建新的镜像。 1.1 dockerfile 文件编写 首先&…...
投了15亿美元,芯片创新公司Ampere为何成了Oracle真爱?
【科技明说 | 科技热点关注】 一个数据库软件公司却想要操控一家芯片厂商,这样的想法不错。也真大胆。 目前,全球数据库巨头甲骨文Oracle已经持有Ampere Computing LLC 29%的股份,并有可能通过未来的投资选择权获得对这家芯片制造…...
vue 报告标题时间来自 elementUI的 el-date-picker 有开始时间和结束时间
要在Vue中使用 Element UI 的 el-date-picker 来选择开始时间和结束时间,并将其展示在报告中,以下是详细的实现步骤。 实现思路: 使用 Element UI 的 el-date-picker 组件,让用户选择时间范围(开始时间和结束时间&am…...
简单几何问题的通解
来,这道题怎么做?边长为2的正方形内,2个扇形的交集面积是多少?这道题一定要画辅助线,因为要用到两个扇形的交点,如果不画辅助线,这个交点相关的4个子图一个都无法求出面积,只能求出子…...
DBeaver导出数据表结构和数据,导入到另一个环境数据库进行数据更新
在工作中,我们会进行不同环境之间数据库的数据更新,这里使用DBeaver导出新的数据表结构和数据,并执行脚本,覆盖另一个环境的数据库中对应数据表,完成数据表的更新。 一、导出 右键点击选中想要导出的数据表࿰…...
【Golang】合理运用泛型,简化开发流程
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...
OpenCV单目相机内参标定C++
基于OpenCV 实现单目相机内参标定: a.使用OpenCV库实现内参标定过程。通过角点检测、亚像素角点定位、角点存储与三维坐标生成和摄像机标定分别获取左右相机的内参。 b.具体地,使用库函数检测两组图像(左右相机拍摄图像)中棋盘格…...
基于MATLAB(DCT DWT)
第三章 图像数字水印的方案 3.1 图像数字水印的技术方案 在数据库中存储在国际互联网上传输的水印图像一般会被压缩,有时达到很高的压缩比。因此,数字水印算法所面临的第一个考验就是压缩。JPEG和EZW(Embedded Zero-Tree Wavelet࿰…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...
Android第十三次面试总结(四大 组件基础)
Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成,用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机: onCreate() 调用时机:Activity 首次创建时调用。…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
