当前位置: 首页 > news >正文

MVP架构深层剖析-从六大设计原则的实现角度到用依赖注入深度解耦

主流架构模式(如 MVC、MVP、MVVM等)的设计本质上都是为了遵循六大设计原则,解决开发中的耦合、可维护性、可测试性等问题。因此理解六大设计原则是掌握架构模式的基础,本文主要以MVP架构的剖析为主线,解析MVP模式在六大原则中的体现及MVP的解耦优化

MVP基础概念

MVP 架构里,将逻辑,数据,界面的处理划分为三个部分,模型(Model)-视图(View)-控制器(Presenter)。各个部分的功能如下:

  • Model 模型,负责数据的加载和存储。
  • View 视图,负责界面的展示。
  • Presenter 控制器,负责逻辑控制。

mvp

MVP 和 MVC 最大的不同,就是 View 和 Model 不相互持有,都通过 Presenter 做中转。View 产生事件,通知给 Presenter,Presenter 中进行逻辑处理后,通知 Model 更新数据,Model 更新数据后,通知数据结构给 Presenter,Presenter 再通知 View 更新界面。

MVP模块上下层关系

从面向对象设计原则来看MVP架构模式,我们先思考一下MVP三层模块间上下模块关系。答案为View是上层模块,Presenter是中层模块,Model是下层模块,理由如下

  • 依赖方向:高层依赖低层,低层 “无感知”

    • 高层模块(View)的功能实现必须依赖中间层(Presenter)(比如 View 需要通过 Presenter 触发登录逻辑);

    • 中间层(Presenter)的业务协调必须依赖低层(Model)(比如 Presenter 需要调用 Model 的login()方法校验账号密码);

    • 反之,低层模块(Model)完全不依赖任何上层:Model 只关注 “如何处理业务”(如 “账号长度是否≥6 位”“密码是否匹配数据库”),不知道 View 的存在也不需要知道 Presenter 的具体实现

  • 职责内聚:高层管 “交互”,低层管 “核心”

    • 高层(View)的职责是 “与用户交互”,属于 “易变层”:比如 UI 样式可能频繁修改(按钮颜色、输入框位置)、交互逻辑可能调整(比如新增 “验证码登录” 选项),但这些变化不会影响核心业务;

    • 低层(Model)的职责是 “核心业务与数据”,属于 “稳定层”:比如登录的核心规则(账号密码校验逻辑、接口请求参数)、数据存储方式(本地 SP 还是数据库),这些一旦确定很少变动;

    • 中间层(Presenter)的作用是 “隔离易变与稳定”:当 View 变动时,只需修改 Presenter 与 View 的交互逻辑,无需改动 Model;当 Model 变动时(如换接口),只需修改 Presenter 调用 Model 的代码,无需改动 View,这正是 MVP 解耦的核心价值。

MVP 通过接口(View 接口和 Presenter 接口)实现了 OCP。通过依赖接口而非具体实现,使得替换 View 的实现(如用于测试的 Mock View)或扩展 Presenter 的功能变得非常容易,而无需改动现有稳定代码

从面向对象设计原则角度剖析MVP

  • 这小节核心从依赖倒置原则出发。高层模块​​ 和 ​低层模块​​ 都依赖于抽象​(接口)。Presenter 依赖于 IView 接口,而不是具体的 Activity。Presenter 通常也依赖于 IMode),而不是具体的网络或数据库操作类。依赖倒置原则是 MVP 架构的核心和灵魂。在传统MVC架构中,Activity(Controller)直接操作 TextView(View)和 HttpClient(Model),这是MVP和MVC架构的核心差异,即MVC架构违反了依赖倒置原则。其他像开闭原则里氏替换原则都可以从这个角度出发,扩展接口的实现类。
  • 单一职责原则来讲,MVP三层分离,分别处理数据,ui与交互逻辑。
  • 迪米特原则来看,​View (Activity)​​ 只知道它的直接朋友 Presenter,也不知道P层的具体实现。​Presenter​ 知道它的两个直接朋友:View 和 Model,但它不知道 View 是一个 Activity 还是一个 Fragment,也不知道 Model 内部的具体实现。这样保证了MVP架构可以单独对P层进行单元测试。
    至于接口隔离原则就具体看程序员的实现了。

MVP的一般实现

下面举一个MVP模式的例子

  • 契约层,将M,V,P层的接口写到一起防止类膨胀
package com.example.mvctest;public interface LoginContract {public interface IModel {public void login(String account, String password, LoginCallback loginCallback);interface LoginCallback{public void onSuccess(String msg);public void onFailure(String msg);}}public interface IView {public String getAccount ();public String getPassword();public void clearText();public void showSuccess(String msg);public void showFailure(String msg);}public interface IPresenter {public void login();public void clearText();}
}
  • Model层,处理数据逻辑。Model层不需要持有Presenter的引用。
public class LoginModel implements LoginContract.IModel{@Overridepublic void login(String account, String password, LoginCallback loginCallback) {new Handler().postDelayed(() -> {if (account.equals("100086") && password.equals("88888888")) {loginCallback.onSuccess("登录成功");} else {loginCallback.onFailure("账号或密码错误");}}, 1500);}
}
  • View层

View层是用户界面的抽象,负责展示数据和接收用户输入,并将交互事件转发给Presenter。通常先定义一个接口,再由Activity或Fragment实现。

public interface LoginView {void showLoginSuccess(); // 登录成功时更新UIvoid showLoginFailure(); // 登录失败时更新UI
}
  • Presenter层

    Presenter作为Model和View之间的桥梁,接收View的请求,调用Model进行数据处理,并根据结果回调View的方法来更新UI

public class LoginPresenter implements LoginContract.IPresenter{LoginContract.IView loginView;LoginContract.IModel loginModel;public LoginPresenter(LoginContract.IView loginView) {this.loginView = loginView;this.loginModel = new LoginModel();}@Overridepublic void login() {String account = loginView.getAccount();String password = loginView.getPassword();loginModel.login(account, password, new LoginContract.IModel.LoginCallback() {@Overridepublic void onSuccess(String msg) {loginView.showSuccess(msg);}@Overridepublic void onFailure(String msg) {loginView.showFailure(msg);}});}@Overridepublic void clearText() {loginView.clearText();}
}

MVP的解耦优化

工厂模式

不过这里在P层的构造函数中,this.loginModel = new LoginModel();仍然存在P层和Model层的耦合,从面向对象六大设计原则来讲,高层模块应该依赖抽象而非低层模块。这样直接new显然会导致P层和M层有较高的耦合,但是如果通过构造参数直接获得,会导致V层创建P层引用时又需要new一个M层,这仍会增加耦合。对于小型项目尚可,对于大型项目后期的维护,如果想要换个实现方式或者进行单元测试,P层代码都不能直接达到要求,需要改动,因此就需要看到下面的工厂模式

我们定义一个用于创建对象的接口,让子类决定实例化哪个类,当我们新增对象创建逻辑时,只需新增工厂类,无需修改已有代码。

// 1. 抽象工厂接口
public interface PresenterFactory {LoginContract.IPresenter createPresenter(LoginContract.IView view);
}// 2. 具体工厂类(实现具体创建逻辑)
public class LoginPresenterFactory implements PresenterFactory {@Overridepublic LoginContract.IPresenter createPresenter(LoginContract.IView view) {return new LoginPresenter(view, new LoginModel());}
}// 3. 扩展另一个工厂(例如测试用的工厂,使用MockModel)
public class TestLoginPresenterFactory implements PresenterFactory {@Overridepublic LoginContract.IPresenter createPresenter(LoginContract.IView view) {return new LoginPresenter(view, new MockLoginModel()); // 使用模拟Model}
}//4. V层代码@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});LoginContract.IPresenterFactory factory = new LoginPresenterFactory();presenter = factory.createPresenter(this);//通过工厂获得presenter的实例binding.btnLogin.setOnClickListener(this);binding.btnClear.setOnClickListener(this);}

上述代码就可以解决P层和M层耦合的问题。尽管这样又引入了View层和工厂类的耦合,但这种耦合是必要且可控的。相对于P层和M层上下层模块的耦合,工厂类的职责单一且稳定(仅负责创建对象),几乎不会频繁变动。即使变动(如换工厂实现),也只需修改 View 中 “获取 Presenter” 的一行代码,影响范围极小。

依赖注入深层解耦

如果上面的工厂模式的解耦还不满足,我们还可以用**DI(依赖注入)**思想,下面直接使用Hilt框架进行讲解,不了解hilt框架可以暂时忽略下面的内容。

我们对Model层注入依赖。具体的做法是定义一个抽象方法,里面定义抽象方法(因为对该函数不需要任何实现,我们也不会调用该方法)。该抽象方法的参数是该接口的实例,返回类型是该接口类型,这样就成功注入了依赖。

@Module
@InstallIn(ActivityComponent.class)
public abstract class ModelModul {@Bindspublic abstract LoginContract.IModel bindPresenter(LoginModel loginModel);
}

注入依赖之后在V层定义接口变量的上面加上注解@Inject即可得到上面注入的LoginModel的实例。

@Inject
public LoginContract.IModel model;
presenter = new LoginPresenter(this, model);

V层获取P层引用时直接传入该Model层变量即可。这样就彻底消除了耦合。如果后面想要换Model层的实现,只需修改抽象方法的参数或者重新加一个抽象方法进行扩展,修改可以完全独立于M,V,P层。加一个抽象方法稍微有些繁琐,需要自定义注解以区分不同的实现,Hilt本身的内容足够作为一篇博客,本篇主要介绍一下解耦思想,不对此进行展开了。

http://www.dtcms.com/a/363399.html

相关文章:

  • 安全芯片助力游戏设备防抄板
  • 「Windows自动化之王:PowerShell极简美学」​
  • 微信小程序 navigateTo 栈超过多层后会失效
  • 【开题答辩全过程】以 基于微信小程序的体育场馆预约管理系统为例,包含答辩的问题和答案
  • 【Git】一文详解Git Rebase和Merge区别 看完必会
  • 整体认识K8s之PriorityClass优先级调度/HPA自动扩缩容机制
  • golang 依赖管理
  • 网络技术名词 CDN NAT GA DNS
  • 深度学习篇---Pytorch常用优化器
  • 力扣72:编辑距离
  • 用 PyTorch 实现食品图像分类:从数据预处理到模型训练与预测
  • mayfly-go:web 版 linux、数据库等管理平台
  • 码农必备!本地调试神器act,GitHub Actions最佳拍档
  • C++ 条件变量,互斥锁
  • vue飞自在酒店管理系统(代码+数据库+LW)
  • 第十七讲:编译链接与函数栈帧
  • Python图像处理模块介绍
  • Linux 文本处理四剑客:cut, sort, uniq, tr
  • springboot redisson 分布式锁切面入门与实战
  • HTML应用指南:利用POST请求获取全国便利蜂门店位置信息
  • 面试tips--JVM(4)--Minor GC Major GC Full GC
  • 从理念到实践:三层解耦架构与“无系统”论
  • 59.螺旋矩阵II
  • 科研界“外挂”诞生了:科学多模态模型Intern-S1-mini开源
  • linux开发板(rk3568,树莓派)自动连接保存好的WIFI
  • 百度网盘基于Flink的实时计算实践
  • SpringMVC —— Spring集成web环境和SpringMVC快速入门
  • 微信小程序列表之分页、刷新、加载更多开发
  • [密码学实战]逆向工程常见工具合集及下载地址(四十七)
  • 顶级天才会思考什么问题