大前端全新 PVState 架构了解一下?
简介
PVState 全称是 Presenter View State。是我创造的一种全新的声明式 UI 下的架构模式,从目前看来,我认为它是声明式 UI 下最好的架构模式。它包含 PState 和 VState。这里的 State 指的是 StatefulWidget 的 State。
这个模式是从传统的 MVC、MVP、MVVM 推演出来的,因此有必要对各个传统架构模式进行分析,看它们为什么会存在?解决了什么问题?
我先抛出一个结论:在 PVState 架构没有出现之前,MVC 是声明式 UI 下最好的架构模式。
MVC
MVC 的全称是 Model View Controller。网上对它的解释众说纷纭,每个人都有不一样的理解。这篇文章不会对 MVC 的概念做一个权威的定义。
在我的理解中,Controller 指代 Activity,它包含 App 的业务逻辑,Model 指代数据本身以及读写数据的数据逻辑。View 指代 XML 所构建的 View 树以及操作 View 树的视图逻辑。
MVC 的最大的问题是 Model 层是解耦的,但 Controller 和 View 没有解耦,这导致在业务逻辑和视图逻辑高度耦合,很可能前一行代码是业务逻辑,后一行代码就是视图逻辑,业务逻辑和视图逻辑相互穿插,大大地降低了代码的可读性和可维护性。
所以 MVC 只做到了 Model - Controller,Model - View 之间的解耦,并没有做到 Controller - View 之间的解耦。
MVP
MVP 是目前业界使用最多的架构模式了,它把原有的业务逻辑挪到了 Presenter 中,通过接口操作 Model 和 View。Activity 充当了 View 的职责。但我觉得它并没有什么可圈可点的地方。通过接口操作 View 和直接操作 View 相差其实不大。Presenter 中仍然存在业务逻辑和视图逻辑相互穿插的情况。
它有好几个好处,但在我看来下面的这些好处意义不大:
1. 将 View 层抽象成接口后,View 可以单独变化,但很少存在 View 变化而 Presenter 不变的情况
2. 可脱离 View,单独对 Presenter 进行测试,扪心自问你真的这么做过么?
3. 可实现视图逻辑与业务逻辑独立开发,可由不同的开发团队分别实现,扪心自问你真的这么做过么?
它的缺点也很明显:
1. 接口爆炸
2. 由于需要定义大量的接口,因此开发效率会降低
MVVM
说到 MVVM,第一时间想到的就是 DataBinding 了吧,在我的 MMP 架构没有出现之前,它确实是 Android 上实现 MVVM 的唯一选择。但无论从哪个方面来评判,它都设计得不尽如人意,因此就不深入探讨了。
对于唯一选择这个措辞,有人可能要反驳了,我想告诉他们,没有数据视图双向绑定的架构不是 MVVM,比如 ViewModel + LiveData。
MVVM 是命令式 UI 下的理想架构,数据变了视图就跟着变,视图变了数据也跟着变。它无需像 MVP 那样定义一大堆接口。理论上你只需要直接操作数据,无需关心 UI。这是不是和声明式 UI 很像呢,但它俩有本质的区别,后面我会写一篇文章来彻底阐释声明式 UI,因为截至目前,我没有看到哪一篇文章把它讲明白过。
MVC 为什么是声明式 UI 下更好的架构模式?
对于这个问题,我们来分析一下。
从 MVC 到 MVP 再到 MVVM 是一个逐步演进优化的过程,演进的核心在于如何降低业务逻辑和视图逻辑的耦合度。
但在声明式 UI 中,业务逻辑和视图逻辑天然是解耦的。视图逻辑被隔离到 build 函数(或 buildXxx)中。业务逻辑被隔离到其它函数中,我们只需要在 build 函数中建立视图和数据的绑定关系。数据变化后调用 setState 刷新 UI 即可。一个典型的 MVC 应用就是 Flutter Counter。
既然视图逻辑和业务逻辑已经天然解耦,那么 MVP、MVVM 就没有意义了。尝试在声明式 UI 下使用 MVP、MVVM 只会把事情搞复杂,所以 MVC 才是声明式 UI 下更好的架构模式。
getX 或 Provider 有点类似 MVP,但我接受不了它们那种用法。我希望在建立视图和数据的绑定关系时,能直接访问到数据,而不是通过 controller.getXxx。
此外,如果视图逻辑和业务逻辑都放在一个 State 类中,可能不好维护,也违背了单一职责。因此 PVState 应运而生了。
PVState 架构
PVState 架构包含 PState 和 VState。这里的 PState 代表 Presenter State,它只处理业务逻辑,VState 代表 View State,它只负责建立视图和数据的绑定关系。VState 继承自 PState,这样可以直接访问 PState 中定义的数据,调用 PState 中定义的方法。
void main() {
runApp(Stateful.of(CounterVState()));
}
import 'package:flutter/material.dart';
import 'package:flutter_constraintlayout/flutter_constraintlayout.dart';
import 'package:flutter_pvstate/pv_state.dart';
/// Presenter state, logic is written here
abstract class CounterPState extends BasePagePState {
final count = obs(0);
void add() {
count.value = count.value + 1;
}
// int count = 0;
//
// void add() {
// setState(() {
// count++;
// });
// }
}
/// View state, view is written here
class CounterVState extends CounterPState with VState {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Home Page'),
),
body: ConstraintLayout(
children: [
const Text(
'You have pushed the button this many times:',
).applyConstraint(
centerTo: parent,
),
/// Local update
ValueListenableBuilder(
valueListenable: count,
builder: (_, value, __) {
return Text(
'$value',
style: Theme
.of(context)
.textTheme
.headline4,
);
},
).applyConstraint(
outBottomCenterTo: rId(0),
),
/// Global update
// Text(
// '$count', // Direct access to count
// style: Theme.of(context).textTheme.headline4,
// ).applyConstraint(
// outBottomCenterTo: rId(0),
// ),
FloatingActionButton(
onPressed: add, // Widget and logic separation
child: const Icon(Icons.add),
).applyConstraint(
bottomRightTo: parent,
margin: const EdgeInsets.only(
right: 20,
bottom: 20,
),
)
],
),
),
);
}
}
PVState 架构只有 114 行代码,它隔离了视图逻辑和业务逻辑,也支持了状态共享、路由栈监听等功能。从目前我的使用情况来看,相当不错。如果配合 Flutter ConstraintLayout 一起使用,那就爽歪歪了!
使用 PVState 时,你只需要继承 State 抽象类,不需要继承 StatefulWidget,如下:
void main() {
runApp(Stateful.of(CounterVState()));
}
结束语
PVState 在我眼中是声明式 UI 下最好的架构模式,在你眼中可能并不是,欢迎大家留言探讨。点击查看原文直达 GitHub。
文章转自公众号:FlutterFirst