Flutter 架构设计: MVVM + Repository

Flutter 架构设计: MVVM + Repository

技术博客 admin 486 浏览

上一篇 用Flutter写可以,但架构可不能少啊(MVC) 此篇为MVVM

一、移动端架构的目标

常说的架构,特别是移动端上的架构核心就一条“高内聚、低耦合”, 一切设计都是为这条例服务的,满足这个条例的项目一定在其扩展性、维护性、健壮性等方面有较好的表现。

就拿Android 来说,Jetpack出现之前官方从未提到过Android 开发推荐使用那种”架构“, 为什么会这样呢,任何一个程序员都知道项目中架构的重要性,官方不可能不知道,那为什么没有推荐呢?

我查找了一下,官方方面提到过”关注点分离原则“, 但是这是架构中的一个标准或者说是依据吧,并且Android 设计中就带有”架构“的韵味,比如UI是在XMl 中写的,逻辑是在Activity中写的,不同的页面是有不同的Activity组成的等等。

二、架构的演进的原因

  1. 代码量增多

    • 随着应用功能的增加,代码量也不断增加。如果没有合理的架构,代码会变得难以管理和维护。
  2. 开发团队扩大

    • 一个项目可能会有多个开发人员参与,如果没有统一的架构规范,不同开发人员的代码风格和编程习惯会导致代码质量参差不齐,增加了项目管理的复杂性。
  3. 质量和工期问题

    • 高内聚、低耦合的架构有助于提高代码质量,减少 Bug,缩短开发周期。

这也就是移动端中会出现MV. 系列的多种架构的原因。 随着项目规模的增大和复杂度的增加,传统的开发方式逐渐暴露出一些问题,如高耦合、难以维护和扩展等。为了解决这些问题,出现了 MV* 系列架构,如 MVC、MVP、MVVM 等。

  1. MVC(Model-View-Controller)

    • 将应用分为 Model、View 和 Controller 三个部分,分别处理数据、界面和业务逻辑。这种分离提高了代码的可维护性和可扩展性。
  2. MVP(Model-View-Presenter)

    • 改进了 MVC 的一些不足,通过引入 Presenter 来处理 View 和 Model 之间的交互,使 View 更加轻量化和易于测试。
  3. 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系列的架构模式呢,我认为也是有必要的。

原因如下,

  1. 让MV + repository 模式进场,能更好的约束团队开发,统一开发模式。
  2. MV系列能让Flutter 开发更加清晰
  3. 引入 MV(Model-View)模式可以显著提高代码的可测试性:
  4. 使用MV模式可以提高代码的组织和可维护性:
  • 清晰的代码结构:将代码划分为不同的模块(如Model、View、ViewModel)可以使项目结构更加清晰,便于开发人员理解和维护。
  • 单一职责原则:每个模块只负责特定的职责(如Model处理数据,View处理UI显示),这有助于保持代码的清晰和简洁,减少模块之间的相互依赖,从而提高代码的可维护性。
  1. MV模式可以显著提高团队协作的效率:
  • 明确的职责分工:团队成员可以根据职责分工来处理不同的模块(如Model、View、ViewModel),这减少了不同开发人员之间的冲突,提高了开发效率。
  • 标准化的开发流程:通过引入统一的架构模式,团队成员可以遵循相同的开发标准和最佳实践,减少了沟通成本和错误率。
  1. 对于复杂应用,引入MV模式可以更好地支持复杂的应用需求:
  • 复杂的业务逻辑处理:MV模式中的ViewModel可以处理复杂的业务逻辑,使得UI部分(View)可以保持简洁和轻量化。
  • 灵活的状态管理:通过ViewModel和Repository,可以实现灵活的状态管理和数据处理,满足复杂应用的需求。

四、MVVM + Repository + (FutureBuilder/ StreamBuilder) + (StringController) 架构

那既然需要 我们就设计一款这方面的架构, 主要目的在于分离关注点,使代码更易于维护和测试

MVVM 模式概述

  1. Model(模型)

    • 负责数据层,包括数据的获取、存储和管理。
    • 通常包括数据类和与数据相关的业务逻辑。
  2. View(视图)

    • 负责 UI 展示,直接与用户交互。
    • 在 Flutter 中,View 通常由 StatelessWidgetStatefulWidget 组成。
  3. ViewModel(视图模型)

  • 负责业务逻辑和状态管理。
  • 通过 ViewModel 进行数据绑定,将数据传递给 View 并响应用户操作。
  • 在 Flutter 中,ViewModel 通常使用 ChangeNotifier 或其他状态管理解决方案(如 Provider、Riverpod、Bloc 等)。

4.1 为了让关注点更加分离,决定使用MVVM + Repository 模式,然后在VM中 M与V的双向关联通信 我们支持两种

  1. 使用StreamController 观察方式更新
  2. 使用FutureBuilder 和StreamBuilder 完成数据的自动处理

这里简单介绍一下FutureBuilder 和StreamBuilder

  • FutureBuilder:用于处理一次性的异步操作。Future 代表一个将在未来某个时间点完成的单一结果。
  • StreamBuilder:用于处理一系列异步数据。Stream 可以持续发出数据,并且可以是无限的。

4.2 使用MVVM+ repository 模式

简单介绍一下repository 模式 Repository 模式(Repository Pattern)是一种软件设计模式,用于分离业务逻辑和数据访问逻辑。它提供了一个抽象层,封装数据源的访问细节,使应用程序的业务逻辑与数据访问逻辑解耦。以下是对 Repository 模式的详细介绍及其关键概念:

4.2.1 关键概念

  1. 抽象层:Repository 模式通过定义接口或抽象类,将数据访问逻辑与业务逻辑分离。这样,业务逻辑层并不直接与数据源交互,而是通过 Repository 进行操作。
  2. 数据源:数据源可以是任何形式的数据存储,例如数据库、Web 服务、本地文件系统等。Repository 负责与这些数据源进行交互,并将数据传递给业务逻辑层。
  3. 单一职责:Repository 模式遵循单一职责原则(SRP),将数据访问逻辑集中在一个类或一组类中,简化了代码维护和扩展。

4.2.2 优点

  1. 解耦:业务逻辑和数据访问逻辑解耦,使得代码更易于维护和测试。
  2. 可测试性:可以使用模拟对象(mock)轻松测试业务逻辑,而无需依赖实际的数据源。
  3. 可替换性:可以轻松更换数据源,而不需要修改业务逻辑代码。例如,可以从 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 示例图

  1. View

    • 显示数据并与用户交互。
    • 监听 ViewModel 的变化并更新 UI。如果使用Futere/StreamBuilder 直接通过ViewModel 获取数据 在builder中进行处理即可, 如果不使用这种懒加载模式,则可以通过StreamController listen监听处理
    • 传递用户操作到 ViewModel。
  2. ViewModel

    目前网上存在的这个模式大多会使用ChangeNotifier 进行数据的双向绑定,但是就项目的结构而言,不是所有的页面都需要他,所以我将其不设置到ViewMdoel中,而是改用StreamController 完成,这个开发时需要的话自己在BaseViewModel 的基础上自己添加

    • 负责处理 UI 逻辑。
    • 从 Repository 获取数据,并将数据转换成 UI 所需的格式。
    • 响应用户交互并更新数据。
  3. Model

    • 定义应用程序的数据结构。
    • 可以包含业务逻辑(例如验证、计算等)。
  4. Repository

    • 提供数据操作的接口,统一管理不同的数据源。
    • 可能包含缓存逻辑,减少数据重复获取。
    • 抽象化数据源,使 ViewModel 无需关心数据获取的具体实现。

4.3.4 Base 封装

  1. 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(); }
  1. BaseRepository
kotlin
复制代码
abstract class BaseRepository { }
  1. 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 示例代码

  1. 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中状态管理是很重要的一环,也有很多的状态管理框架,使用方面我的建议是:

  1. scoped_model 比较适合新手和小型轻度的项目;
  2. flutter_redux、Provider更适合中大型项目。在挑选框架时,flutter_redux更适合有前端开发经验的人,Provider更适合拥有App开发经验的人使用。而 Provider、flutter_redux 会更倾向于用在需要数据共享或者相对复杂的业务封装中,比如登录状态、用户信息、主题和多语言切换等
  3. BLoC,一般适用于和业务模块结合使用,针对每个业务模块如LoginBLoC、UserBLoC等用于独立页面内的状态管理。

源文:Flutter 架构设计: MVVM + Repository

如有侵权请联系站点删除!

Technical cooperation service hotline, welcome to inquire!