MVI设计模式

news2024/11/16 1:16:04

一.各种框架对比

https://blog.csdn.net/qq_36390114/article/details/126160017

1. MVC(Model-View-Controller)

在这里插入图片描述
模型-视图-控制器
MVC的目的就是为了M和V代码分离,降低耦合性。

Model:数据来源,网络请求数据和数据库数据。
View:对应xml布局文件和动态的布局部分。
Controller:逻辑控制部分。主要起到协调M层和V层的关系,起承上启下的作用。

优点:
一定程度上实现了代码分离,降低代码的耦合性。
缺点:
1.Controller和View层难以完全解耦,而且随着业务逻辑增多,2.Controller会变的越来越臃肿。在Android中Activity充当Controller的角色,后面Activity会变成GadActivity。
3. M层和V层还有交互,没有做到完全分离。

2. MVP(Model-View-Presenter)

在这里插入图片描述
模型-视图-提供者
MVP是在MVC的基础上发展过来的,实现了M层和V层的完全分离,进一步代码解耦。

Model:数据来源,网络请求数据和数据库数据。
View:对应xml布局文件和动态的布局部分。对应Activity。
Presenter:逻辑控制部分。通过接口连接M层和V层。

优点:
1.V层对应Activity,只负责UI的展示和P层直接通信,和M层没有任何交互。
2.V层没有和M层有交互,可以抽成单独的组件,方便复用。
3.代码结构清晰,P层可以用于多个视图,而不需要改变P层的逻辑。
4.V层和M层完全分离,方便协同工作,只需要专注做视图或者逻辑控制部分,不用关系对方的逻辑。比如负责逻辑控制部分不用等设计出图,就可以直接写代码,并进行单元测试。
5.代码复用率高,方便单元测试。
缺点:

1.M层和V层都需要和P层进行通许,会导致P层代码很复杂,而且都是通过接口通讯,如果一个P层用于多个Activity,所有Activity都要实现全部都接口,无论能不能用得到。修改P层接口,往往会涉及到很多个界面,很麻烦。
2.P层和V层通过接口通讯,会持有View的引用,容易造成内存泄露
3.随着业务增多,P层即使只对应一个视图,接口也会越来越多。

3. MVVM(Model-View-ViewModel)

在这里插入图片描述

模型-视图-视图模型
MVVM在MVP的基础上进一步解耦,VM层不在持有View的引用。
引入响应式编程、Lifecycle生命周期感知、LiveData数据存储、DataBinding数据绑定概念。配合Goole提供的Jetpack组件能节省很多代码。

Model:数据来源,网络请求数据和数据库数据。
View:对应xml布局文件和动态的布局部分。对应Activity。向比较MVP这里的View是通过DataBinding来进行双向绑定数据。
ViewModel:逻辑控制部分。作为桥梁,通知M层处理数据,并将结果回调到V层处理UI。Activity持有ViewModel引用,ViewModel不持有View的引用。

优点:

1.进一步解耦,ViewModel不持有View的引用,当V层改遍,只要V层绑定的数据不变,ViewModel就不需要修改。
2.不需要写很多样板代码,省略findViewById(),Activity变得很简介。
3.通过DataBinding实现View和Model的实时改变,一方改变就会同步到对方。
缺点:

1.DataBinding双向绑定采用异步更新数据,对ListView这样的列表,效率比较低。而且在实际开发中,设计出的Ui效果比较复杂,数据绑定不能完全实现,往往只使用到DataBinding一半的功能,数据更新部分还是手动实现。
2.DataBinding会自动生成大量的代码和属性字段
3.复杂的页面要定义多个LiveData,并且都要暴露为不可变的LiveData。
4.LiveData是粘性事件,数据倒灌等问题。解决方法:使用第三方 UnPeekLiveData

4. MVI(Model-View-Intent)

在这里插入图片描述

模型-视图-意图
出现的目的是为了解决MVVM中双向绑定数据的不足。把双向绑定变成单向数据流。使用Flow代替LiveData存储数据。

Model:这里的Model不是其他框架中的Model层,在MVI框架中表示存储UI的状态。
View:在MVI中View层主要是接口,负责相应UI的状态。
Intent:在MVI中Intent(和Android中的Intent不是同一个)主要负责传递UI状态。使用sealed关键字,形成一个封闭类,类似枚举。主要用于V层通知ViewModel处理数据。
State:和Intent一样是一个封闭类,主要用于ViewModel回调数据修改UI。

优点:

1.UI的所有变化来自State,所以只需聚焦State,架构更简单、易于调试
2.数据单向流动,很容易对状态变化进行跟踪和回溯
3.State实例都是不可变的,确保线程安全
4.UI只是反应State的变化,没有额外逻辑,可以被轻松替换或复用
缺点:
1.所有的操作最终都会转换成State,所以当复杂页面的State容易膨胀
2.State是不变的,每当State需要更新时都要创建新对象替代老对象,这会带来一定内存开销

5.总结

mvc:activity即是v也是c,代码臃肿
mvp:接口太多,p层容易内存泄漏
mvvm:databinding生成的代码态度,livedata数据倒灌/粘性
mvi:复杂页面,state膨胀

面试官问:非常小的项目用什么?MVC

二.MVI简单实现

1.MVI

mvi的改动在于将View和ViewModel之间的多数据流改为基于ViewState的单数据流,MVI分为四个部分:

  • View: Activity 和xml文件,与其他模式中的View的概念相同。
  • Intent: 定义数据操作,将数据传到Model的唯一来源。
  • ViewModel: 存储视图状态,负责处理表现逻辑,并将ViewState设置给可观察数据容器
  • ViewState: 一个数据类,包含页面状态和对应的数据。

2.MVI特点

  • 唯一可信源:数据只有一个来源(ViewModel),与MVVM思想相同
  • 单向数据流:状态向下流动,事件向上流动。
  • 响应式:ViewState包含页面当前状态和数据,View通过订阅
  • ViewState就可以完成页面刷新。相比于 MVVM 是新的特性。

3.单向数据流

界面变化是数据流的末端,界面消费上游产生的数据,并随上游数据的变化进行刷新。

在这里插入图片描述
在这里插入图片描述

状态向下流动,事件向上流动的这种模式称为单向数据流

MVI强调数据的单向流动,主要分为几步:

1.用户操作以Intent的形式通知Model.

2.Model基于Intent更新State

3.View接收到State变化刷新UI

数据永远在一个环形结构中单向流动,不能反向流动。

4.缺点:

  • State 膨胀: 所有视图变化都转换为 ViewState,还需要管理不同状态下对应的数据。实践中应该根据状态之间的关联程度来决定使用单流还是多流;

  • 内存开销: ViewState 是不可变类,状态变更时需要创建新的对象,存在一定内存开销;

  • 局部刷新: View 根据 ViewState 响应,不易实现局部 Diff 刷新,可以使用 Flow#distinctUntilChanged() 来刷新来减少不必要的刷新。

5.代码实现

在这里插入图片描述
(1)http网络封装

data class ApiResponse<T>(val code:Int,val message:String,val data:T)
data class VideoEntity(
    val address: String,
    val authname: String,
    val caption: String,
    val dianzan: Int,
    val guanzhu: Int,
    val headpath: String,
    val id: Int,
    val like_count: Int,
    val publishtime: String,
    val type: Int,
    val videomainimg: String,
    val videopath: String,
    val view_count: Int
)
interface MyService {
    @GET("/video/findVideos?currentPage=1&pageSize=10")
    suspend fun  video():ApiResponse<MutableList<VideoEntity>>
}
class RetrofitManager {
    companion object{

        fun getRetrofit():Retrofit{
            var client = OkHttpClient.Builder()
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .connectTimeout(30, TimeUnit.SECONDS)
                .addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
                .build()
            return Retrofit.Builder()
                .client(client)
                .baseUrl("http://43.143.146.165:8181")
                .addConverterFactory(GsonConverterFactory.create())//自动解析
          //      .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//call--》Observable
                .build()
        }

    }
}

(2)Intent

sealed class MyIntent {
    object GetVideos:MyIntent()//请求数据
}

(3)UIState

sealed class MyUIState{
    data class Success(var videoList:MutableList<VideoEntity>?):MyUIState()
    data class Error(var exception:Throwable):MyUIState()
    //object Loading:MyUIState()
}

(4)ViewModel

class MyViewModel:ViewModel() {
    val newsChannel = Channel<MyIntent>(Channel.UNLIMITED)
    private val _uiState = MutableStateFlow<MyUIState>(MyUIState.Success(null))
    //
    val uiState:StateFlow<MyUIState> = _uiState
    init {
        hanldeIntent()
    }

    private fun hanldeIntent() {
        viewModelScope.launch {
            newsChannel.consumeAsFlow().collect{
                when(it){
                    is MyIntent.GetVideos -> getVideoData()
                }
            }
        }
    }
    fun getVideoData(){
        viewModelScope.launch {
            myVideos.flowOn(Dispatchers.Default)
                .catch { exception->
                    _uiState.value = MyUIState.Error(exception)
                }.filter {
                    it != null
                }.collect {
                    _uiState.value = MyUIState.Success(it.data)
                }
        }
    }
    private var myVideos:Flow<ApiResponse<MutableList<VideoEntity>>> = flow {
         val video = RetrofitManager.getRetrofit().create(MyService::class.java).video()
        emit(video)

    }



}

(5)activity

class MainActivity : AppCompatActivity() {
    private lateinit var myViewModel: MyViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        lifecycleScope.launch{
            myViewModel.uiState.collect{
                when(it){
                    is MyUIState.Success ->{
                        updateUI(it)
                    }
                    is MyUIState.Error ->{
                        Log.d("ytx", "失败了: "+it.exception.message)
                    }
                }
            }
        }



    }

    fun updateUI(uiState: MyUIState.Success){
        val size = uiState.videoList?.size
        Toast.makeText(this,"$size",Toast.LENGTH_SHORT).show()
    }

    fun doGet(view: View){
        lifecycleScope.launch{
            myViewModel.newsChannel.send(MyIntent.GetVideos)
        }
    }


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/655784.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DJ8-1 shell 的启动和终止、重定向、管道

目录 8.1 shell 的启动和终止 8.2 输入输出重定向 8.2.0 标准输入输出 8.2.1 输出重定向 > 8.2.2 输入重定向 < 8.2.3 常见输入输出重定向形式 8.2.4 标准错误输出重定向 8.3 管道 Linux 系统中的 shell 具有两大功能&#xff1a; 是一个命令解释器&…

express框架学习笔记

express简介 express是一个基于Node.js平台的极简的、灵活的WEB应用开发框架。express是一个封装好的工具包&#xff0c;封装了很多功能&#xff0c;便于我们开发WEB应用&#xff08;HTTP服务&#xff09; express使用 新建express文件夹新建文件test01.js&#xff0c;代码如…

特征点Features2D类介绍

文章目录 Features2D类介绍1. cv::AgastFeatureDetector2. cv::AKAZE3. cv::BRISK4. cv::FastFeatureDetector5. cv::GFTTDetector6. cv::KAZE7. cv::MSER8. cv::SimpleBlobDetector9. cv::StarDetector10. cv::SIFT11. cv::SURF12. cv::FastFeatureDetector13. cv::AgastFeatu…

leetcode2385. 感染二叉树需要的总时间(java)

感染二叉树需要的总时间 感染二叉树需要的总时间递归 dfs代码演示 二叉树专题 感染二叉树需要的总时间 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/amount-of-time-for-binary-tree-to-be-infected 给你一棵二叉树的…

每日一练 | 华为认证真题练习Day60

1、启用GRE的keepalive功能后&#xff0c;GRE隧道的本端会周期性的每10s向对端发送一次keepalive报文。 A. 对 B. 错 2、AAA协议是RADIUS协议。 A. 对 B. 错 3、路由器Radius信息配置如下&#xff1a;下列说法正确的有&#xff1f;&#xff08;多选&#xff09; A. 计费服务…

[Spring Cloud]:学习笔记·(一)

文章目录 摘要1 认识微服务1.1 单体架构与分布式架构1.2 分布式架构与微服务1.3 微服务架构 摘要 摘要&#xff1a;分布式&#xff1b;微服务&#xff1b;springcloud 1 认识微服务 1.1 单体架构与分布式架构 架构方式解释优点缺点单体架构将业务所有功能集中在一个项目中开…

java从入门到起飞(二)——运算符

目录 前提——运算符概念算数运算符注意事项&#xff1a;字符的“”操作字符串的“”操作 赋值运算符注意事项&#xff1a; 自增自减运算符注意事项&#xff1a; 关系运算符注意事项&#xff1a; 逻辑运算符短路逻辑运算符注意事项&#xff1a; 三元运算符计算规则&#xff1a;…

OpenCV Mat类

文章目录 Mat类Mat类数据类型读取Mat类支持的运算OpenCV Mat数据类型指针ptr的使用多维矩阵创建 Mat类 Mat类数据类型读取 S 有符号整型 U 无符号整型 F 浮点型CV_8U - 8位无符号整数&#xff08;0…255&#xff09;CV_8S - 8位有符号整数&#xff08;-128…127&#xff…

Python基础(1)——Python简介

Python基础&#xff08;1&#xff09;——Python简介 文章目录 Python基础&#xff08;1&#xff09;——Python简介目标Python介绍Python版本总结 目标 了解PythonPython的应用领域Python的版本 Python介绍 Python是时下最流行、最火爆的编程语言之一&#xff0c;具体原因如…

SAP从入门到放弃系列之BOM组-Part1

目录 BOM组两种模式&#xff1a; 创建BOM的方式 方式一&#xff1a;直接在每个工厂分别创建BOM。 方式二&#xff1a;创建BOM组&#xff0c;然后每个工厂参考创建 方式三&#xff1a;创建BOM组&#xff0c;每个工厂参考创建&#xff0c;针对有特殊的工厂复制BOM组后进行调…

人机交互学习-9 以用户为中心的设计

以用户为中心的设计 以用户为中心的设计思想工程设计过程的三个假设以人为本设计思想三个方面的假设以用户为中心的设计四原则UDC项目包含的方法 用户参与设计用户参与的重要性选择哪些用户&#xff1f;为什么要让用户参与&#xff1f; 用户参与的形式参与式设计PICTIVECARD 理…

Nacos-手写注册中心基本原理

本文已收录于专栏 《中间件合集》 目录 概念说明需求分析核心功能代码实现AService模块BService模块NacosService模块NacosSDK模块 注意事项总结提升 概念说明 注册中心是微服务架构中的纽带&#xff0c;类似于“通讯录”&#xff0c;它记录了服务和服务地址的映射关系。在分布…

香蕉派(Banana Pi) BPI-M2 Zero 评测试,与树莓派 Zero同尺寸的开发板

Banana Pi M2 Zero 是一款微型计算机&#xff0c;配备四核处理器并内置 Wi-Fi 和蓝牙。这是一款非常适合基本计算任务甚至轻度游戏的小型设备。在这篇评论中&#xff0c;我们将了解 M2 Zero 的性能、功能和价值。 什么是 Banana Pi BPI-M2 Zero Banana Pi M2 Zero 是由深圳公…

MongoDB(Windows版)安装

首先需要下载 官网&#xff1a;MongoDB: The Developer Data Platform | MongoDB 安装过程 需要安装的版本 第一步&#xff1a;安装时&#xff0c;Custom是指可以自定义安装路径&#xff0c;然后傻瓜式安装即可&#xff08;注意&#xff1a;先不要安装图形化工具&#xff0…

2023-06-17 LeetCode每日一题(分割圆的最少切割次数)

2023-06-17每日一题 一、题目编号 2481. 分割圆的最少切割次数二、题目链接 点击跳转到题目位置 三、题目描述 圆内一个 有效切割 &#xff0c;符合以下二者之一&#xff1a; 该切割是两个端点在圆上的线段&#xff0c;且该线段经过圆心。该切割是一端在圆心另一端在圆上…

【Vue】学习笔记-Vue Router activated deactivated 路由守卫

Vue Router activated deactivated 路由守卫 activated deactivated路由守卫1.全局守卫2.独享守卫3.组件内守卫全局路由守卫路由器的两种工作模式 activated deactivated activated 和 deactivated 是路由组件所独有的两个钩子&#xff0c;用于捕获路由组件的激活状态 具体使用…

管理类联考——英语——趣味篇——阅读——考题的来源

Part One考研英语阅读——Part A 1.卫报 《卫报》( The Guardian)是英国的全国性综合内容日报。与《泰晤士报》、《每日电讯报》被合称为英国三大报。由约翰爱德华容泰勒创办于1821年5月5日。该报注重报道国际新闻&#xff0c;擅长发表评论和分析性专题文章。一般公众视《卫报…

【数据分析之道-Matplotlib(九)】Matplotlib棉棒图

文章目录 专栏导读1、Matplotlib棉棒图stem()基本语法2、Matplotlib棉棒图stem()定义样式2.1linefmt参数2.2markerfmt参数2.3举例一&#xff1a;直线样式2.4举例二&#xff1a;圆点样式 3、棉棒图案例实战3.1绘制每月销量的棉棒图3.2绘制每月销量与平均销量之差 专栏导读 ✍ 作…

Prometheus介绍安装和快速入门

Prometheus介绍安装和快速入门 1、Prometheus介绍 1.1 什么是 Prometheus? Prometheus&#xff08;普罗米修斯&#xff09;是古希腊的一个神明&#xff0c;名字的意思是「先见之明」。从它的名字可以看出&#xff0c; Prometheus 是做「先见之明」的监控告警用途。维基百科…

HJ26 字符串排序

题目&#xff1a; HJ26 字符串排序 题解&#xff1a; 规则 1 &#xff1a;英文字母从 A 到 Z 排列&#xff0c;不区分大小写。 统一转换&#xff0c;通过减去对应字母的起始值&#xff0c;得到一个相对值&#xff0c;抹平大小写&#xff0c;例如&#xff1a;B - A&#xff…