一、MVC(Model-View-Controller)
MVC是比较直观的架构模式,用户操作->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结果反馈给View)。
MVC使用非常广泛,比如JavaEE中的SSH框架(Struts/Spring/Hibernate),Struts(View, STL)-Spring(Controller, Ioc、Spring MVC)-Hibernate(Model, ORM)以及ASP.NET中的ASP.NET MVC框架,xxx.cshtml-xxxcontroller-xxxmodel。(实际上后端开发过程中是v-c-m-c-v,v和m并没有关系,下图仅代表经典的mvc模型)
数据关系
View 接受用户交互请求
View 将请求转交给Controller
Controller 操作Model进行数据更新
数据更新之后,Model通知View更新数据变化
View 更新变化数据
方式:所有方式都是单向通信
使用
MVC中的View是可以直接访问Model的!从而,View里会包含Model信息,不可避免的还要包括一些业务逻辑。在MVC模型里,更关注的Model的不变,而同时有多个对Model的不同显示,及View。所以,在MVC模型里,Model不依赖于View,但是 View是依赖于Model的。不仅如此,因为有一些业务逻辑在View里实现了,导致要更改View也是比较困难的,至少那些业务逻辑是无法重用的。
二、MVP(Model-View-Presenter)
MVP是把MVC中的Controller换成了Presenter(呈现),目的就是为了完全切断View跟Model之间的联系,由Presenter充当桥梁,做到View-Model之间通信的完全隔离。
.NET程序员熟知的ASP.NET webform、winform基于事件驱动的开发技术就是使用的MVP模式。控件组成的页面充当View,实体数据库操作充当Model,而View和Model之间的控件数据绑定操作则属于Presenter。控件事件的处理可以通过自定义的IView接口实现,而View和IView都将对Presenter负责。
数据关系
View 接收用户交互请求
View 将请求转交给 Presenter
Presenter 操作Model进行数据更新
Model 通知Presenter数据发生变化
Presenter 更新View数据
MVP的优势
Model与View完全分离,修改互不影响
更高效地使用,因为所有的逻辑交互都发生在一个地方—Presenter内部
一个Preseter可用于多个View,而不需要改变Presenter的逻辑(因为View的变化总是比Model的变化频繁)。
更便于测试。把逻辑放在Presenter中,就可以脱离用户接口来测试逻辑(单元测试)
方式
各部分之间都是双向通信
Presenter处理事件,执行相应的逻辑(UI的逻辑和业务逻辑),这些逻辑映射到Model操作Model。那些处理UI如何工作的代码基本上都位于Presenter。
三、MVVM(Model-View-ViewModel)
如果说MVP是对MVC的进一步改进,那么MVVM则是思想的完全变革。它是将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的,因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上。
这方面典型的应用有.NET的WPF,js框架Knockout、AngularJS等。
https://juejin.im/entry/583690d6d20309005fea9fd5
https://juejin.im/collection/5836fca16a22654ac5d16915
MVP模式的流程图如下:
采用MVP模式的优势是:
- 把业务逻辑抽离到Presenter层中,View层专注于UI的处理。
- 分离视图逻辑与业务逻辑,达到解耦的目的。
- 提高代码的阅读性。
- Presenter被抽象成接口,可以根据Presenter的实现方式进行单元测试。
- 可拓展性强。
用MVP模式实现简单的登录逻辑的方式:
- 项目的结构:
从上面的代码结构图可看出,用MVP模式实现登陆模块需要创建6个文件,分别是M、V、P接口文件和接口的对应实现。其中LoginActivity就是View层的具体实现。这样的好处时Activity组件只需要负责处理UI相关逻辑就可以了,而相关的业务逻辑全部抽象到Presenter层中处理。通过这种方式能够很好的避免传统Android开发中的Activity/Fragment等UI组件既负责处理UI逻辑又处理业务逻辑的结果。
. 代码实现
说了这么多,最后我们来看看代码的实现吧。
ILoginModel
public interface ILoginModel { void login(String name ,String password); }
ILoginPresenter
public interface ILoginPresenter { void loginToServer(String userName,String password); void loginSucceed(); }
ILoginView
public interface ILoginView { void showProgress(boolean enable); void showLoginView(); }
上面是登陆模块对应的MVP接口的具体设计,下面我来简单介绍一下接口中的几个方法:
ILoginModel.login(String name ,String password)
登陆方法,通过该方法向服务器发送登陆请求。ILoginPresenter. loginToServer (String name ,String password)
通知Model响应登陆事件。ILoginPresenter. loginSucceed()
当登陆事件完成时(成功/失败),Model层要通知该方法登陆事件已完成。ILoginView. showProgress(boolean enable)
当Presenter层调用loginToServer (String name ,String password)
方法时,要通过该方法通知View层显示加载动画。ILoginView. showLoginView()
登陆成功时,Presenter层会通过该方法通知View层登陆已成功。
下面我们来看看这几个接口的具体实现。
- LoginModel
public class LoginModel implements ILoginModel{ private ILoginPresenter presenter; private Handler mHandler = new Handler(); public LoginModel(ILoginPresenter presenter) { this.presenter = presenter; } @Override public void login(String name ,String password) { mHandler.postDelayed(new Runnable() { @Override public void run() { Log.d("LoginModel", "run: "); presenter.loginSucceed(); } },2000); } }
上面的Model层实现了login(String name,Stringpassword)
登陆方法,该方法的具体实现逻辑是通过线程休眠2秒来模拟网络登陆的过程,登陆成功后会通过LoginPresenter的loginSucceed()
方法来通知Presenter层登陆结果。实际开发中我们需要根据具体的业务逻辑来实现该过程。
- LoginPresenter
public class LoginPresenter implements ILoginPresenter{ private ILoginModel loginModel; private ILoginView loginView; public LoginPresenter(ILoginView loginView) { this.loginView = loginView; this.loginModel = new LoginModel(this); } @Override public void loginToServer(String userName, String password) { loginView.showProgress(true); loginModel.login(userName,password); } @Override public void loginSucceed() { loginView.showProgress(false); loginView.showLoginView(); }
从上面代码可以看出LoginPresenter的实现逻辑很简单,首先在构造方法中获取ILoginView对象并撞见ILoginModel对象。然后当View层调用loginToServer(String userName, String password)
方法成功时,通知View层显示加载动画并调用ILoginModel层的login(String userName, String password)
方法向服务器发送登陆请求。当登陆成功后(即Model层通知loginSucceed方法时)通过loginView.showProgress(false)
方法通知View层隐藏加载动画,并通知View登陆成功。
- LoginActivity
对于LoginActivity我们只需要关注其中的几个方法即可loginBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //模拟登陆,不需要账号密码 loginPresenter.loginToServer("",""); } });
@Override
public void showProgress(boolean enable) {
if (enable){
progressBar.setVisibility(View.VISIBLE);
loginLayout.setVisibility(View.GONE);
}else {
progressBar.setVisibility(View.GONE);
loginLayout.setVisibility(View.VISIBLE);
}
}
@Override
public void showLoginView() {
Toast.makeText(LoginActivity.this,"登陆功",Toast.LENGTHSHORT).show();
finish();
}
上面时实现了ILoginView接口的两个方法。
结合上面的代码可以看出,当点击登陆按钮的监听事件时,我们不需要关注业务逻辑,只需要调用loginPresenter.loginToServer("","");
方法即可,然后根据实际情况实现View层中ILoginView接口的方法即可,这样达到了UI业务与逻辑完全分离的目的。