背景
Flutter
作为最近很火的一个跨平台技术,以其高性能、跨平台的一系列优秀特性成功吸引了很多开发者和组织的青睐,但是由于其不同于传统Android
或iOS
开发的Widget
机制,使得视图的代码往往冗长、不够简洁,解决这种困境的方法就是在开发中合理地运用合适的架构模式,使得程序的视图与数据分离,这样视图层的代码只用专心进行视图的描述和操作即可,不涉及过多复杂的数据操作,这样就可以使视图层的代码达到简洁。由于Flutter
目前没有官方推荐的项目架构,而且笔者也未遇到大家都说好用的架构模式,故此,笔者基于MVP
的架构,设计了一套我个人比较青睐的架构模式,本文将详细介绍,希望可以和大家一起沟通、探索,力争衍生出一套适合Flutter
的架构模式,从而大大提高生产力,如果文中有什么地方大家觉得设计的不合理的,大可提出,我们一起讨论。
从实例看模式
本文将基于一个经典的登录/注册需求展开描述,将登录/注册的界面与逻辑分离,以求达到解耦,我们首先想想,对于一个注册/登录需求,按照传统MVP
的思想,应该如何将其视图与逻辑分离,大致的流程应该是这样的:
IView
、IPresenter
、IModel
三个接口分别是View
、Presenter
、Model
类应该实现(Implements
)的接口View
类中持有IPresenter
类的实例,记做IPresenter
(实际上是Presenter
类的实例,因为Presenter
类实现了IPresenter
接口),当View
中的登录按钮被点击时,调用iPresenter
对象的登录方法Presenter
类中持有IView
和IModel
类的实例,记做iView
和iModel
(实际上是View
类和Model
类的实例,因为View
类和Model
类分别实现了IView
接口和IModel
接口),当Presenter
的登录方法被调用时,调用iModel
对象的登录方法Model
类持有IPresenter
的实例,记做IPresenter
(实际上是Presenter
类的实例,因为Presenter
类实现了IPresenter
接口),Model
类的登录方法被调用后,进行网络操作,发起登录请求,获取登录的结果(成功或失败),并调用iPresenter
对象的方法将结果回调给Presenter
层Presenter
接收到Model
的回调之后调用iView
对象的回调方法,将结果继续回调给View
层进而实现视图更新,整个操作完成
总结一下,IView
、IPresenter
、IModel
三个接口分别应该包含的方法如下:
IView
:登录/注册操作、结果回调操作
IPresenter
:登录/注册操作、结果回调操作
IModel
:登录/注册操作
可以看到,IView
、IPresenter
、IModel
三个接口都具有登录/注册操作,而IView
、IPresenter
都具有结果回调操作,这样在定义接口的时候,登录/注册操作的声明会被写3遍、结果回调操作的声明会被写2遍,代码上会出现臃肿和冗余。所以我对此进行了改进,改进示意图如下:
将登录/注册操作这类M、V、P都应该具有的操作抽取到IFunction
这个接口中,然后让IView
、IPresenter
、IModel
这三个接口实现(implements
)IFunction
这个接口,将结果回调操作抽取到ICallBack
这个接口中,让IView
、IPresenter
这两个接口实现(implements
)这个接口,然后,让View
实现(implements
)IView
接口,让Presenter
实现(Implements
)IPresenter
这个接口,让Model
实现(Implements
)IModel
这个接口,这样,一个登录操作的完成流程是这样的:
View
类持有IPresenter
类的实例,记做iPresenter
(实际上是Presenter
类的实例,因为Presenter
类实现了IPresenter
接口),当View
中的登录按钮被点击时,调用iPresenter
对象的登录方法Presenter
类中持有IView
和IModel
类的对象,记做iView
和iModel
(实际上是View
类和Model
类的实例,因为View
类和Model
类分别实现了IView
接口和IModel
接口),当Presenter
类的登录方法被调用时,调用iModel
对象的登录方法Model
类持有ICallBack
类的实例,记做iCallBack
(实际上是Presenter
类的实例,因为Presenter
类实现了IPresenter
接口,而IPresenter
接口实现了ICallBack
接口),Model
类的登录方法被调用后,进行网络操作,发起登录请求,获取登录的结果(成功或失败),并调用iCallBack
对象的回调方法将结果回调给iPresenter
Presenter
接收到Model
的回调之后调用iView
对象的实例,将结果继续回调给View
层进而实现视图更新,整个操作完成
这样,我们使用IFunction
和ICallBack
分别将登录/注册操作和结果回调操作进行封装,然后使IView
、IPresenter
、IModel
分别实现自己应该具有的功能,最后让View
、Presenter
、Model
分别实现IView
、IPresenter、IModel
接口,一步一步实现了视图与数据的解耦。
代码实现
以上是理论分析部分,落实在代码中,我来大家一步一步地实现:
首先定义我们所说的登录/注册操作对应的LoginInterface
接口:
enum RequestType { LOGIN, SIGNUP }
class LoginInterface {
//这里通过RequestType区分当前操作是登录操作还是注册操作
logInOrSignUp(String userName, String passWord, RequestType requestType) {}
}
然后来定义回调方法对应的ILoginCallBack
接口:
class ILoginCallBack {
//登录/注册成功
logInOrSignUpSuccess(User user, String describe) {}
//登录/注册失败
logInOrSignUpFailed(String describe) {}
}
然后来定义IView
接口,因为View
应该有登录/注册、回调两个功能,因此应该让IView
接口实现LoginInterface
、ILoginCallBack
两个接口:
class ILoginPage implements LoginInterface,ILoginCallBack {
showProsess(bool show) {}
@override
logInOrSignUp(String userName, String passWord, RequestType requestType) {
// TODO: implement logInOrSignUp
return null;
}
@override
logInOrSignUpFailed(String describe) {
// TODO: implement logInOrSignUpFailed
return null;
}
@override
logInOrSignUpSuccess(User user, String describe) {
// TODO: implement logInOrSignUpSuccess
return null;
}
}
注意,这里只是定义接口,所以方法不用实现。
定义IPresenter
接口,由于Presenter
应该有登录/注册、回调两个功能,因此应该让IView
接口实现LoginInterface
、ILoginCallBack
两个接口:
class ILoginPagePresenter implements LoginInterface, ILoginCallBack {
@override
logInOrSignUp(String userName, String passWord, RequestType requestType) {
// TODO: implement logInOrSignUp
return null;
}
@override
logInOrSignUpFailed(String describe) {
// TODO: implement logInOrSignUpFailed
return null;
}
@override
logInOrSignUpSuccess(User user, String describe) {
// TODO: implement logInOrSignUpSuccess
return null;
}
}
最后定义IModel
接口,其只需要登录/注册一个功能即可,因此让IModel
接口实现Logininterface
这一个接口即可:
class ILoginModel implements LoginInterface {
@override
logInOrSignUp(String userName, String passWord, RequestType requestType) {
// TODO: implement logInOrSignUp
return null;
}
}
然后我们来实现View
、Presenter
、Model
三个类:
View
:
class _LoginState extends State<LoginPage> implements ILoginPage{
ILoginPagePresenter iLoginPagePresenter;
@override
void initState() {
iLoginPagePresenter = LogInPresenter(this);
super.initState();
}
@override
logInOrSignUp(String userName, String passWord, RequestType requestType) {
iLoginPagePresenter.logInOrSignUp(userName, passWord, requestType);
}
@override
logInOrSignUpFailed(String describe) {
// TODO: 登录/注册失败,更新视图
}
@override
logInOrSignUpSuccess(User user, String describe) {
// TODO: 登录/注册成功,更新视图
}
}
Presenter
:
class LogInPresenter implements ILoginPagePresenter {
ILoginPage _iLoginPage;
ILoginModel _iLoginModel;
LogInPresenter(this._iLoginPage) {
_iLoginModel = LoginModel(this);
}
@override
logInOrSignUp(String userName, String passWord, RequestType requestType) {
_iLoginModel.logInOrSignUp(userName, passWord, requestType);
}
@override
logInOrSignUpFailed(String describe) {
_iLoginPage.logInOrSignUpFailed(describe);
}
@override
logInOrSignUpSuccess(User user, String describe) {
_iLoginPage.logInOrSignUpSuccess(user, describe);
}
}
Model
:
class LoginModel implements ILoginModel {
ILoginCallBack _iLoginCallBack;
LoginModel(this._iLoginCallBack);
@override
logInOrSignUp(String userName, String passWord, RequestType requestType) {
// TODO: 发起网络操作进行登录/注册
_iLoginCallBack.logInOrSignUpSuccess(user, describe);
}
}
至此我们就对登录/注册这个功能完成了架构改造,可以发现,经过架构改造之后,整个项目的条理非常清晰,避免了视图层代码太过冗余的痛点,利于维护、测试。
总结
本文针对Flutter
中的登录/注册场景进行了架构改造,整个逻辑是基于MVP
,解决了MVP
中接口冗余的痛点,当然本架构还存在很多问题,欢迎大家及时指出,一起交流。