Flutter研发阶段性总结(三)基本设计模式MVP
在上篇内容的基础上,我们已经可以写⼀些简单的应⽤了,但是可以预见到的是,随着页⾯功能的增多,函数个数、数据与页⾯的交互也会随之增对,随之⽽来的就是散落在各处的函数和setState,代码就越来越难维护了。因此需要适当的引⼊⼀些设计模式,将界⾯、数据与逻辑解耦。这⾥⾸先提⼀下MVP。
什么是MVP?
⽹络上⽂章很多。。。。。就不重复了。
Flutter上的MVP实践
这⾥直接拿代码说话,先说说MVP的具体实践,然后举个例⼦:同⼀个功能页⾯切换不同的数据源。
1. 基础MVP结构声明
Dart语⾔貌似是没有interface的,这⾥⽤的是abstract class。
abstract class IView<T>{
setPresenter(T presenter);
}
abstract class IPresenter {
init();
}
2. 声明具体页⾯的Presenter和View接⼝
假设我们有⼀个页⾯在加载的时候会从web接⼝获取数据并显⽰,同时页⾯可以响应⽤户的输⼊,⽐如上拉加载更多。则从页⾯⾏为层⾯我们可以抽象如下接⼝:
abstract class DemoPagePresenter implements IPresenter {
// 初始化时候加载第⼀批数据
void loadData();
// ⽤户操作加载更多
void loadMore();
}
abstract class DemoPageView implements IView<DemoPagePresenter>{
// 第⼀批数据加载成功
void onLoadSuccess(data);
// 第⼀批数据加载失败
void onLoadError();
// 更多数据加载成功
void onLoadMoreSuccess(data);
// 更多数据加载失败
void onLoadMoreError();
}
从这⾥可以看出, Presenter 主要负责事件驱动或者⽤户触发后的业务⾏为,在这⾥包括了页⾯初始化时候的加载数据loadData 和⽤户上拉列表时候的 loadMore 。
⽽View主要负责页⾯逻辑处理后的结果展现,⽐如加载成功、失败分别显⽰什么。mvp
3. 接⼝的具体实现
在Presenter和View都声明完毕后,即可以开始落地具体的实现代码了。这⾥我们⽤假代码描述。
1)Presenter的具体实现
Presenter 仅关⼼事件驱动和⽤户触发后的逻辑,并决定分别另View采⽤何种⾏动,本⾝并不关⼼View长什么样。此时的View对于Presenter⽽⾔,仅仅是⼏个抽象的接⼝函数。
class DemoPagePresenterImpl implementes DemoPagePresenter {
// 这⾥Presenter需要引⼊View的实例,来出发View的刷新
DemoPageView _view;
// View的实例也需要引⼊Presenter的实例引⽤
DemoPagePresenterImpl(this._view){
_view.setPresenter(this);
}
// 这⾥泛指数据的查询服务,⽐如可以是Dio,或者Sqlite的查询封装
DataRepository _repository;
int currentPage =0;
@override
init(){
// 初始化数据查询服务
_repository =DataRepository();
}
/
/ 实现具体的业务逻辑,并决定采⽤何种View的操作,不关⼼View的函数背后的具体实现
@override
loadData(){
_repository.loadByPage(currentPage).then((data){
_LoadSuccess(data);
}).catchError((error){
_LoadError();
});
}
// 实现具体的业务逻辑,并决定采⽤何种View的操作,不关⼼View的函数背后的具体实现
@override
loadMore(){
_repository.loadByPage(++currentPage).then((data){
_LoadMoreSuccess(data);
}).catchError((error){
_LoadMoreError();
});
}
}
2)View的具体实现
View在Flutter中⼀般就和Widget绑定在⼀起了,让Widget implements 相应的View接⼝即可。注意在这⾥需要对View和Presenter的绑定操作做⼀定的处理,同时完成Presenter的init初始化。
请注意: 这⾥的State是View, State中的Presenter变量在State构造函数中就通过setPresenter指定好了。
这样View仅关⼼何时出发Presenter的业务处理代码,但是具体业务代码是如何实现的View并不关⼼。
class DemoPage extends StatefulWidget {
@override
_DemoPageState createState(){
_DemoPageState view =new _DemoPageState();
// 在这⾥初始化Presenter,同时与View建⽴绑定关系
DemoPagePresenter presenter =new DemoPagePresenterImpl(view);
presenter.init();
return view;
}
}
class _DemoPageState extends State<DemoPage> implementes DemoPageView {
DemoPagePresenter _presenter;
List _resultList =List();
@override
Widget build(BuildContext context){
return Column(
children:<Widget>[
RaisedButton(
onPressed:(){ _presenter.loadMore();},
onPressed:(){ _presenter.loadMore();},
child:Text('Press to load more')
),
ListView.builder(
itemCount: _resultList.length,
builder:(context, index){
return Text(_resultList[index]);
}
),
]
);
}
@overrid
setPresenter(DemoPagePresenter presenter){
_presenter = presenter;
}
@overrid
initState(){
super.initState();
// 初始化加载第⼀批数据
_presenter.loadData();
}
@override
onLoadSuccess(data){
setState((){
_resultList = data;
});
}
@override
onLoadError(){
print('error!');
}
@override
onLoadMoreSuccess(data){
setState((){
_resultList.addAll(data);
});
}
@override
onLoadMoreError(){
print('load more error!');
}
}
进阶:切换数据源
改需求最直接的反映就是开发环境和⽣产环境的切换了,在上边的例⼦来说就是根据需求切换DataRepository的不同实现类即可。这⾥截取部分⽚段
// 这⾥泛指数据的查询服务,⽐如可以是Dio,或者Sqlite的查询封装  DataRepository _repository;
int currentPage =0;
@override
init(){
// 初始化数据查询服务
// _repository = DataRepository();
// 修改为如下:
if(appConstants.production){
_repository =DataRepositoryProductionImpl();
}else{
_repository =DataRepositoryDevelopmentImpl();
}
}