Flutter 研发阶段性总结(三) 基本设计模式MVP

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tzdwsy/article/details/93401135

在上篇内容的基础上,我们已经可以写一些简单的应用了,但是可以预见到的是,随着页面功能的增多,函数个数、数据与页面的交互也会随之增对,随之而来的就是散落在各处的函数和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主要负责页面逻辑处理后的结果展现,比如加载成功、失败分别显示什么。

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) {
      _view.onLoadSuccess(data);
    }).catchError((error){
      _view.onLoadError();
    });
  }

  // 实现具体的业务逻辑,并决定采用何种View的操作,不关心View的函数背后的具体实现
  @override
  loadMore() {
    _repository.loadByPage(++currentPage).then((data) {
      _view.onLoadMoreSuccess(data);
    }).catchError((error){
      _view.onLoadMoreError();
    });
  }
}

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(); },
          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();
    }
  }
展开阅读全文

没有更多推荐了,返回首页