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

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.xdnf.cn/news/19633.html

相关文章:

  • Elasticsearch 核心知识与常见问题解析
  • MCU上跑AI—实时目标检测算法探索
  • 【 HarmonyOS 6 】HarmonyOS智能体开发实战:Function组件和智能体创建
  • 空间不足将docker挂载到其他位置
  • 03_网关ip和端口映射(路由器转发)操作和原理
  • 梯度消失问题:深度学习中的「记忆衰退」困境与解决方案
  • React 学习笔记4 Diffing/脚手架
  • 2025了,你知道electron-vite吗?
  • 网络原理——HTTP/HTTPS
  • ImageMagick命令行图片工具:批量实现格式转换与压缩,支持水印添加及GIF动态图合成
  • 2条命令,5秒安装,1秒启动!Vite项目保姆级上手指南
  • 鸿蒙NEXT界面交互全解析:弹出框、菜单、气泡提示与模态页面的实战指南
  • 开源的聚合支付系统源码/易支付系统 /三方支付系统
  • Erlang 利用 recon 排查热点进程
  • 人工智能之数学基础:分布函数对随机变量的概率分布情况进行刻画
  • 微信小程序 navigateTo 栈超过多层后会失效
  • 在 Delphi 5 中获取 Word 文档页数的方法
  • 小程序蓝牙低功耗(BLE)外围设备开发指南
  • 365 天技术创作手记:从一行代码到四万同行者的相遇
  • C++多线程编程:std::thread, std::async, std::future
  • Jenkins Pipeline 语法
  • 第 12 篇:网格边界安全 - Egress Gateway 与最佳实践
  • python中的zip() 函数介绍及使用说明
  • 基于Spark的新冠肺炎疫情实时监控系统_django+spider
  • HTML第三课:特殊元素
  • 跨境电商账号风控核心:IP纯净度与浏览器指纹的防护策略
  • 跳出“中央集权”的泥潭:以Data Mesh重构AI时代的活性数据治理
  • MySQL8.0 新特性随笔
  • css中 ,有哪些⽅式可以隐藏页⾯元素? 区别?
  • 详细介绍RIGHT JOIN及其用法