上一篇 用Flutter写可以,但架构可不能少啊(MVC) 此篇为MVVM
一、移动端架构的目标
常说的架构,特别是移动端上的架构核心就一条“高内聚、低耦合”, 一切设计都是为这条例服务的,满足这个条例的项目一定在其扩展性、维护性、健壮性等方面有较好的表现。
就拿Android 来说,Jetpack出现之前官方从未提到过Android 开发推荐使用那种”架构“, 为什么会这样呢,任何一个程序员都知道项目中架构的重要性,官方不可能不知道,那为什么没有推荐呢?
我查找了一下,官方方面提到过”关注点分离原则“, 但是这是架构中的一个标准或者说是依据吧,并且Android 设计中就带有”架构“的韵味,比如UI是在XMl 中写的,逻辑是在Activity中写的,不同的页面是有不同的Activity组成的等等。
二、架构的演进的原因
-
代码量增多:
- 随着应用功能的增加,代码量也不断增加。如果没有合理的架构,代码会变得难以管理和维护。
-
开发团队扩大:
- 一个项目可能会有多个开发人员参与,如果没有统一的架构规范,不同开发人员的代码风格和编程习惯会导致代码质量参差不齐,增加了项目管理的复杂性。
-
质量和工期问题:
- 高内聚、低耦合的架构有助于提高代码质量,减少 Bug,缩短开发周期。
这也就是移动端中会出现MV. 系列的多种架构的原因。 随着项目规模的增大和复杂度的增加,传统的开发方式逐渐暴露出一些问题,如高耦合、难以维护和扩展等。为了解决这些问题,出现了 MV* 系列架构,如 MVC、MVP、MVVM 等。
-
MVC(Model-View-Controller) :
- 将应用分为 Model、View 和 Controller 三个部分,分别处理数据、界面和业务逻辑。这种分离提高了代码的可维护性和可扩展性。
-
MVP(Model-View-Presenter) :
- 改进了 MVC 的一些不足,通过引入 Presenter 来处理 View 和 Model 之间的交互,使 View 更加轻量化和易于测试。
-
MVVM(Model-View-ViewModel) :
- 利用了数据绑定(Data Binding)技术,使得 ViewModel 可以自动通知 View 数据的变化,进一步减少了 View 和 Model 之间的耦合。
开发发展过程中,代码量的增多、参与人规模的增大等问题导致了一系列意想不到的问题,比如10个人10中写法,偶数人喜欢将网络请求部分写到Acuity中的内部类里,有的人甚至喜欢将布局引用传递到了工具类中进行更新。那导致的问题就是“低内聚,高耦合”,给业务上导致的结果就是工期一拖再拖,质量一差再差。
为了解决这些问题,出现了按照部分固定的逻辑去写固定的内容,在项目初期,由专门的人建立base约束,后续开发的人按照这个约束进行开发,以此来减少上述的问题。
在base约束的过程中,这些人逐渐考虑如何将代码做的更加好维护、好扩展、更健壮。
后来官方推荐了MVI 架构,这是随着时间的推移和 Android 平台的成熟,Google 逐渐认识到规范化架构的重要性,特别是在开发规模和复杂度不断增加的背景下。这也是为什么在 Jetpack 出现之前,官方更多是提供工具和组件,而不是架构指导。
三、Flutter 中需要MV系列的架构吗
Flutter开发过程中 ,Flutter的定位就是事件驱动的声明式UI框架, 它是UI, 但他不是纯正的UI,它里面不仅通过widget 进行UI描述,还有逻辑的存在,比如网络请求,数据展示,逻辑处理等。
官方对于Flutter设计之初呢,就有架构模式的概念在里面,比如他的提供的各种框架的“状态”,通过提供多种状态管理解决方案,Flutter 鼓励开发者将状态管理和业务逻辑从 UI 组件中分离出来,从而实现高内聚低耦合的设计。这不仅提高了代码的可维护性,还增强了组件的重用性和独立性。
那还有没有必要在插入一些类似于MV系列的架构模式呢,我认为也是有必要的。
原因如下,
- 让MV + repository 模式进场,能更好的约束团队开发,统一开发模式。
- MV系列能让Flutter 开发更加清晰
- 引入 MV(Model-View)模式可以显著提高代码的可测试性:
- 使用MV模式可以提高代码的组织和可维护性:
- 清晰的代码结构:将代码划分为不同的模块(如Model、View、ViewModel)可以使项目结构更加清晰,便于开发人员理解和维护。
- 单一职责原则:每个模块只负责特定的职责(如Model处理数据,View处理UI显示),这有助于保持代码的清晰和简洁,减少模块之间的相互依赖,从而提高代码的可维护性。
- MV模式可以显著提高团队协作的效率:
- 明确的职责分工:团队成员可以根据职责分工来处理不同的模块(如Model、View、ViewModel),这减少了不同开发人员之间的冲突,提高了开发效率。
- 标准化的开发流程:通过引入统一的架构模式,团队成员可以遵循相同的开发标准和最佳实践,减少了沟通成本和错误率。
- 对于复杂应用,引入MV模式可以更好地支持复杂的应用需求:
- 复杂的业务逻辑处理:MV模式中的ViewModel可以处理复杂的业务逻辑,使得UI部分(View)可以保持简洁和轻量化。
- 灵活的状态管理:通过ViewModel和Repository,可以实现灵活的状态管理和数据处理,满足复杂应用的需求。
四、MVVM + Repository + (FutureBuilder/ StreamBuilder) + (StringController) 架构
那既然需要 我们就设计一款这方面的架构, 主要目的在于分离关注点,使代码更易于维护和测试
MVVM 模式概述
-
Model(模型) :
- 负责数据层,包括数据的获取、存储和管理。
- 通常包括数据类和与数据相关的业务逻辑。
-
View(视图) :
- 负责 UI 展示,直接与用户交互。
- 在 Flutter 中,View 通常由
StatelessWidget
或StatefulWidget
组成。
-
ViewModel(视图模型) :
- 负责业务逻辑和状态管理。
- 通过 ViewModel 进行数据绑定,将数据传递给 View 并响应用户操作。
- 在 Flutter 中,ViewModel 通常使用
ChangeNotifier
或其他状态管理解决方案(如 Provider、Riverpod、Bloc 等)。
4.1 为了让关注点更加分离,决定使用MVVM + Repository 模式,然后在VM中 M与V的双向关联通信 我们支持两种
- 使用StreamController 观察方式更新
- 使用FutureBuilder 和StreamBuilder 完成数据的自动处理
这里简单介绍一下FutureBuilder 和StreamBuilder
-
FutureBuilder
:用于处理一次性的异步操作。Future
代表一个将在未来某个时间点完成的单一结果。 -
StreamBuilder
:用于处理一系列异步数据。Stream
可以持续发出数据,并且可以是无限的。
4.2 使用MVVM+ repository 模式
简单介绍一下repository 模式 Repository 模式(Repository Pattern)是一种软件设计模式,用于分离业务逻辑和数据访问逻辑。它提供了一个抽象层,封装数据源的访问细节,使应用程序的业务逻辑与数据访问逻辑解耦。以下是对 Repository 模式的详细介绍及其关键概念:
4.2.1 关键概念
- 抽象层:Repository 模式通过定义接口或抽象类,将数据访问逻辑与业务逻辑分离。这样,业务逻辑层并不直接与数据源交互,而是通过 Repository 进行操作。
- 数据源:数据源可以是任何形式的数据存储,例如数据库、Web 服务、本地文件系统等。Repository 负责与这些数据源进行交互,并将数据传递给业务逻辑层。
- 单一职责:Repository 模式遵循单一职责原则(SRP),将数据访问逻辑集中在一个类或一组类中,简化了代码维护和扩展。
4.2.2 优点
- 解耦:业务逻辑和数据访问逻辑解耦,使得代码更易于维护和测试。
- 可测试性:可以使用模拟对象(mock)轻松测试业务逻辑,而无需依赖实际的数据源。
- 可替换性:可以轻松更换数据源,而不需要修改业务逻辑代码。例如,可以从 SQL 数据库切换到 NoSQL 数据库。
4.3 开始设计
4.3.1 确定组件
- View:负责显示数据和捕获用户交互。 在Flutter中 设计他的State 即可,StatelessWidget 不做考虑
- ViewModel:处理 UI 逻辑,提供数据给 View,并响应用户交互。
- Model:表示应用程序的数据和业务逻辑。
- Repository:管理数据源,提供统一的数据访问接口。
4.3.2 数据流
- View 和 ViewModel:双向绑定,ViewModel 通过数据绑定将数据传递给 View,View 通过事件将用户交互传递给 ViewModel。 (Model 中的数据为了能具备双向绑定的一些功能 需要检测的数据变量可以使用StreamController 观察)
- ViewModel 和 Repository:ViewModel 调用 Repository 获取或存储数据。
- Repository 和 数据源:Repository 与实际的数据源(如网络或本地数据库)交互。
4.3.3 示例图
-
View:
- 显示数据并与用户交互。
- 监听 ViewModel 的变化并更新 UI。如果使用Futere/StreamBuilder 直接通过ViewModel 获取数据 在builder中进行处理即可, 如果不使用这种懒加载模式,则可以通过StreamController listen监听处理
- 传递用户操作到 ViewModel。
-
ViewModel:
目前网上存在的这个模式大多会使用ChangeNotifier 进行数据的双向绑定,但是就项目的结构而言,不是所有的页面都需要他,所以我将其不设置到ViewMdoel中,而是改用StreamController 完成,这个开发时需要的话自己在BaseViewModel 的基础上自己添加
- 负责处理 UI 逻辑。
- 从 Repository 获取数据,并将数据转换成 UI 所需的格式。
- 响应用户交互并更新数据。
-
Model:
- 定义应用程序的数据结构。
- 可以包含业务逻辑(例如验证、计算等)。
-
Repository:
- 提供数据操作的接口,统一管理不同的数据源。
- 可能包含缓存逻辑,减少数据重复获取。
- 抽象化数据源,使 ViewModel 无需关心数据获取的具体实现。
4.3.4 Base 封装
- BaseViewModel
Flutter 中MVVM的状态管理有很多的框架,其中大家经常在此模式中使用的是Provider, 我在这里使用最原始的StreamController (会实现类似于Jetpack LiveData 的效果),我这里为了演示项目的结构 不使用这种模式的架构 直接使用StreamController
scala
复制代码
abstract class BaseViewModel<R extends BaseRepository> {
late R repository;
BaseViewModel() {
repository = createRepository();
}
R createRepository();
void release();
}
- BaseRepository
kotlin
复制代码
abstract class BaseRepository {
}
- BaseState
scala
复制代码
abstract class BaseState<T extends StatefulWidget, VM extends BaseViewModel>
extends State<T> {
late VM viewModel;
@override
void initState() {
super.initState();
viewModel = createViewModel();
observer();
loadData();
}
VM createViewModel();
void observer();
void loadData();
@override
Widget build(BuildContext context);
@override
void dispose() {
super.dispose();
viewModel.release();
}
}
4.3.5 示例代码
- view 页面
scala
复制代码
class UserListPage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _UserListState();
}
class _UserListState extends BaseState<UserListPage, UserListViewModel> {
late List<UserListPageModel> mList = [];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User List'),
),
body: ListView.builder(
itemCount: mList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(mList[index].name),
);
},
),
);
}
/**
* 创建ViewModel
*/
@override
UserListViewModel createViewModel() => UserListViewModel();
/**
* 发起网络请求
*/
@override
void loadData() {
viewModel.getUserLists();
}
/**
* 数据监听
*/
@override
void observer() {
viewModel.userListController.stream.listen((list) {
setState(() {
mList.clear();
mList.addAll(list);
});
});
}
}
五、总结
在Flutter开发中,采用MVVM架构模式具有显著的优势。通过分离关注点,MVVM使代码更易于维护和测试,并且提高了代码的组织性和可读性。引入Repository模式进一步解耦了业务逻辑和数据访问逻辑,增强了代码的可测试性和可扩展性。结合使用FutureBuilder和StreamBuilder,可以有效管理异步操作和数据流,使得UI更新更加简洁高效。
在实际项目中,设计并实施MVVM + Repository架构模式,可以确保团队成员在统一的开发规范下工作,减少沟通成本,提高开发效率。这样的架构不仅适用于简单应用,更能支持复杂应用的需求,使Flutter开发更加规范和高效。
演示中使用最基本的StreamController 做了数据绑定是因为想侧重说明这个结构在关注点问题上的体现,在开发中很多框架可以代替这部分,在Flutter中状态管理是很重要的一环,也有很多的状态管理框架,使用方面我的建议是:
- scoped_model 比较适合新手和小型轻度的项目;
- flutter_redux、Provider更适合中大型项目。在挑选框架时,flutter_redux更适合有前端开发经验的人,Provider更适合拥有App开发经验的人使用。而 Provider、flutter_redux 会更倾向于用在需要数据共享或者相对复杂的业务封装中,比如登录状态、用户信息、主题和多语言切换等
- BLoC,一般适用于和业务模块结合使用,针对每个业务模块如LoginBLoC、UserBLoC等用于独立页面内的状态管理。
源文:Flutter 架构设计: MVVM + Repository
如有侵权请联系站点删除!
技术合作服务热线,欢迎来电咨询!