目录
一 MVC
1.概念
2.实例
(1)Model层
(2)Controller层(包括View层)
3.MVC总结
二 MVP
1.概念
2.实例
(1)Model层
(2)View层
(3)Presenter层
3.总结
(1)MVP优点
(2)与MVC的对比
三 总结
做了Android开发很长时间了,从毕业就一直从事Android开发。现在在翻看自己以前项目中的代码,发现自己以前想法写到代码不是一般的烂。最近也在做一些技术沉淀,发现需要学习的东西还有好多,每天觉得时间飞快。最近也是将公司项目架构调整了一下。以前项目也就是简单的MVC架构模式,当公司业务越来越复杂的时候,发现这种分层方式的弊端越来越明显,Activity中堆积了大量的代码,业务复杂的Activity,代码都有几千行。代码可读性极差,后期维护性差,想想如果哪天产品要对页面UI进行升级的话,其实改造成本还蛮高的,Activity代码逻辑复杂,可重用性不高,所以针对这种现象,将公司项目架构做了调整。
在Android开发的界面显示,通常在开发过程中都会将网络层请求封装成可以之间调用的方法供Activity直接调用,其实就可以看作是一个简单的MVC架构模式。简单的与MVC的三层进行对比下
View(视图)层:主要就是布局文件或者使用java代码实现的自定义view;
Model(模型)层:主要就是渲染UI时使用的数据model、网络请求、数据库增删改查、IO等操作封装对应的相关模块;
Controller(控制)层:主要Activity承担该角色,用来初始化Model层,初始化、加载View层,从而控制View层和Model层。当用户在Activity通过View层触发事件的时候,Activity(Controller层)就会调用Model层的相关代码来获取渲染UI使用的数据,从而达到改变UI,完成用户的交互。
其实在Android中,由于布局文件无法进行一些逻辑和交互,而Activity去承担了View的一些UI渲染和交互的工作,所以在Android中MVC更像是Controller和Model之间的一个数据流向。
通常会将Model层设计成ModelInteface的方式,不对Controller层(Activity)提供具体的实现,这样可以提高Model层的代码的扩展和维护性。举个例子Model层通常用来网络请求,说不定哪天就会更新网络请求的具体实现方式。那么如果通过ModelInteface的方式,那么我们只需要在最新的网络请求方式上去实现ModelInteface,而不需要更改Controller层的代码。
举个代码实例:例如有一个登录的界面,用户输入账号和密码就可以完成登录,返回用户的信息更新到UI界面上(PS:实例代码中都是模拟网络请求过程,仅仅用来说明MVC的一种代码实现方式)。
public class MvcCacheBean implements IViewCacheBean {
public User user;
}
public interface IMvcModelInterface {
/**
* 登录
*
* @param account
* @param password
* @param result 服务器处理完数据返回的结果
*/
void login(String account, String password, IHttpResult result);
}
public class MvcCacheBean implements IViewCacheBean {
public User user;
}
其中的Thread与Handler仅仅用来模拟网络请求的过程,在实际项目中肯定对应这相应封装的网络请求模块对应的API。
public class MvcModelImpl implements IMvcModelInterface {
private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
//仅仅简单的做个逻辑判断,只是用来返回成功和失败的回调
int arg1 = msg.arg1;
IHttpResult result = (IHttpResult) msg.obj;
if (arg1 == 1) {
User user = new User();
user.name = "小刘";
user.sex = "女";
user.age = "34";
MvcCacheBean cacheBean = new MvcCacheBean();
cacheBean.user = user;
result.success(cacheBean);
} else {
result.failure("登录失败\n账号输入数字即可验证成功的逻辑");
}
break;
}
}
};
@Override
public void login(String account, String password, IHttpResult result) {
//模拟具体的网络请求方式的实现:子线程去请求数据,数据处理完之后返回的UI线程
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = 1;
msg.obj = result;
msg.arg1 = TextUtils.isDigitsOnly(account) ? 1 : 2;
handler.sendMessage(msg);
}
}).start();
}
}
在Activity中加载布局文件,实例化控件,并且实例化Model层。当用户填写账号和密码之后,点击Button,去完成网络请求,返回用户信息。
public class MvcActivity extends Activity {
private IMvcModelInterface model;
private TextView tvUserInfo;
private EditText etAccount;
private EditText etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
//初始化View
initWidget();
//初始化Model
model = new MvcModelImpl();
}
private void initWidget() {
tvUserInfo = findViewById(R.id.tv_user_info);
etAccount = findViewById(R.id.et_account);
etPassword = findViewById(R.id.et_password);
}
/**
* 登录button的点击事件
*
* @param view
*/
public void btnLogin(View view) {
tvUserInfo.setText("稍等1s之后就可以看到模拟的结果");
tvUserInfo.setTextColor(Color.BLACK);
String account = etAccount.getText().toString();
String password = etPassword.getText().toString();
if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) {
Toast.makeText(MvcActivity.this, "账号和密码不能为空", Toast.LENGTH_SHORT).show();
return;
}
//调用Model层进行网络请求
model.login(account, password, new IHttpResult() {
@Override
public void success(IViewCacheBean cacheBean) {
MvcCacheBean mvcCacheBean = (MvcCacheBean) cacheBean;
User user = mvcCacheBean.user;
tvUserInfo.setText(String.format("用户登录成功\n用户名:%s\n性别:%s\n年龄:%s", user.name, user.sex, user.age));
tvUserInfo.setTextColor(Color.GREEN);
}
@Override
public void failure(String error) {
tvUserInfo.setText(error);
tvUserInfo.setTextColor(Color.RED);
}
});
}
}
从代码实例中分析事件流向:Activity负责初始化Model层和View层(Controller层控制Model层和View层),当用户触发Button的点击事件(View层产生事件),Activity会调用Model层的相关代码(Controller层向Model层发出请求),当Model层完成业务逻辑,会回调给Activity的成功和失败的结果(Model层处理完数据后,向Controller层通知事件处理完),Activity更新TextiVew的显示(Controller层通知View层处理事件),这样就完成了一个MVC的事件流向的过程。
(1)具有一定分层,Model层彻底解耦,但是Controller层和View层却无法解耦
(2)层与层之间尽量使用回调、消息机制,避免直接持有
(3)在Android中的Activity承担了太多的工作,既有Controller层功能,也要承担View层的功能,所以当UI界面越来越复杂,功能越来越多的时候,造成Activity的代码臃肿。
在Android开发中正因为Activity承担了太多的工作,既要初始化UI,还要去处理用户的交互,并不能单纯的去做Controller,造成Activity代码臃肿,所以就演变除了MVP。
View(视图)层:主要就是布局文件或者使用java代码实现的自定义view,更准确的说其实是Activity和布局文件共同来承担该角色,用来初始化UI和UI交互的工作;
Model(模型)层:和MVC中的Model层功能一致,仍然是渲染UI时使用的数据model、网络请求、数据库增删改查、IO等操作封装对应的相关模块;
Presenter层:作为View层和Model层的中间纽带,用来出来用户交互逻辑,通常持有的是View 的ViewInterface,Presenter通过ViewInterface来处理View层的交互,降低与View层的耦合,更可以不依赖于View,单独对Presenter进行单元测试。
ViewInterface:就是View要实现的接口类。该接口类主要工作就是View的交互事件,我们可以通过该接口类,将Presenter不依赖于View层代码。
另外我觉得可以在增加一个Presenter层增加一个PresenterInterface,这样可以规范Presenter代码,在后面提到的代码生成工具中会有比较好的作用。
那么MVP的三层之间的关系就变成了下图的事件流向:
在MVP中,我们将之前MVC中Activity中的一部分逻辑转移到Presenter中,Activity只需要去初始化UI和完成控件本身的事件监听,当需要逻辑处理的时候,直接调用Presenter层进行处理,而Presenter层直接去调用Model层的代码去完成数据请求;当Model层处理完数据之后,再有Presenter层来通知View层更新UI。现在我们将Activity的控制权交到了Presenter层,而Activity更多的用来转发请求。
仍然通过上面的一个实例简单的说明下MVP的一个架构情况。其实我们从第一部分的概念描述中可以看到,其实MVP和MVC的View层和Model层的功能是一样的,为了主要突出他们有区别的地方,所以在MVP中仍然采用MVC的Model层和View层的代码。
同MVC中的Model层
这次Activity和布局文件同时承担View层的工作。
在介绍Presenter层代码的时候,会发现Presenter层持有的只有该ViewInterface,可以使得Presenter更加灵活。
public interface IMvpViewInterface {
/**
* 接口登录成功回调
* @param cacheBean
*/
void loginSuccess(MvcCacheBean cacheBean);
/**
* 接口登录失败的回调
* @param error
*/
void loginFailure(String error);
}
public class MvpActivity extends Activity implements IMvpViewInterface {
private MvpPresenter presenter;
private TextView tvUserInfo;
private EditText etAccount;
private EditText etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
//初始化View
initWidget();
//初始化Presenter
presenter = new MvpPresenter(MvpActivity.this);
}
private void initWidget() {
tvUserInfo = findViewById(R.id.tv_user_info);
etAccount = findViewById(R.id.et_account);
etPassword = findViewById(R.id.et_password);
}
public void btnLogin(View view) {
tvUserInfo.setText("稍等1s之后就可以看到模拟的结果");
tvUserInfo.setTextColor(Color.BLACK);
String account = etAccount.getText().toString();
String password = etPassword.getText().toString();
//View不需要关系业务逻辑,只需到时候在回调的方法里面填写代码即可
presenter.login(account, password);
}
/**
* 只需要在成功和失败回调之后处理UI,使代码更加简洁
*
* @param cacheBean
*/
@Override
public void loginSuccess(MvcCacheBean cacheBean) {
MvcCacheBean mvpCacheBean = (MvcCacheBean) cacheBean;
User user = mvpCacheBean.user;
tvUserInfo.setText(String.format("用户登录成功\n用户名:%s\n性别:%s\n年龄:%s", user.name, user.sex, user.age));
tvUserInfo.setTextColor(Color.GREEN);
}
@Override
public void loginFailure(String error) {
tvUserInfo.setText(error);
tvUserInfo.setTextColor(Color.RED);
}
}
因为我们项目现在页面大部分代码是可以直接通过自研的工具生成,通过接口规范化Android和iOS两端的代码,那么开发人员可以只需要关注View层代码逻辑即可。并且可以让一个人同时开发两端代码,因为差别在于View层的部分逻辑的差异。
public interface IMvpPresenterInterface {
void login(String account, String password);
}
在Presenter中仅仅持有ViewInterface,那么一个Presenter可以应用在多个View,例如项目中肯定有一些功能是可以用在不同页面的,那么就可以把这些功能放到一个Presenter中,只需要View实现对应的ViewInterface,那么该功能就可以直接在View中使用。
public class MvpPresenter implements IMvpPresenterInterface {
private IMvpViewInterface mvpViewInterface;
private IMvcModelInterface model;
private Context context;
public MvpPresenter(IMvpViewInterface viewInterface) {
this.mvpViewInterface = viewInterface;
this.context = (Context) viewInterface;
//实例化Model
model = new MvcModelImpl();
}
@Override
public void login(String account, String password) {
//部分UI逻辑可以转移到MvpPresenter,来减轻Activity的负担
if (TextUtils.isEmpty(account) || TextUtils.isEmpty(password)) {
Toast.makeText(context, "账号和密码不能为空", Toast.LENGTH_SHORT).show();
return;
}
model.login(account, password, new IHttpResult() {
@Override
public void success(IViewCacheBean cacheBean) {
mvpViewInterface.loginSuccess((MvcCacheBean) cacheBean);
}
@Override
public void failure(String error) {
mvpViewInterface.loginFailure(error);
}
});
}
}
从代码中来分析事件的流向:Activity负责初始化View(View层),同样还是用户点击了这个Button,就会调用到Presenter层相关代码,而Presenter层接到请求之后,会调用Model层的代码来完成数据请求,Model层响应数据之后,Presenter层又会调用到View层的相应的方法完成回调。
现在Activity(View层)仅仅就是View的展示以及View的交互,Activity(View层)不在关心处理的逻辑过程是什么,只需要去做初始化,事件的监听,在对应的回调方法中处理UI显示就可以了,Activity(View层)责任大大减少;
而现在Presenter是整个控制中心,不仅要去决定请求Model层的哪个代码,并且Model层返回数据之后,Prensenter还要决定回调View层的哪个方法,Activity(View层)和Model层不需要做什么,Presenter会安排Activity(View层)和Model层要做什么。
Activity(View层)仅仅就是单独的UI操作,而Presenter来处理交互逻辑,并负责协调Model层和View层。 与上面MVC的代码对比,Activity代码更加简洁。
MVVM是对MVP的再一次升级,其中VM为ViewModel,可以理解为View的数据模型和Presenter的合体。我觉得这样可以拿着微信小程序的开发方式考虑一下。一个页面对应着四个文件:.js文件:主要负责业务逻辑;.wxml:负责页面;.wxss:负责页面样式,修饰页面;.json:负责页面的一些简单配置。我觉得里面就有一个很意思的东西,我们通常都是在.js中定义一个变量,wxml文件中引用该变量,当我们在js中修改该变量的值的时候,wxml文件中自动反馈出该变化,我觉得MVVM应该差不多就是这个意思。
对比下三者之间的异同点:
相同点:三者对应的Model层和View层的功能是相同的。View层就是UI以及一些相关的界面逻辑代码;Model层就是数据对象和对网络请求、数据库等操作。
不同点:怎么将View层和Model层进行通信
(1)MVC:通过Controller层,负责对View层进行初始化和对Model层的操作,Model层的数据变更并不通过Controller层,而是直接通过回调通知View层。
(2)MVP:通过Presenter层来接收View层变化,并决定操作那个Model;而Model数据处理完之后,Presenter层又会去决定操作哪个View来完成整个事件。
(3)MVVM:VM中不仅仅包含View的一些数据属性还包含一些操作,就是微信小程序的wxml文件和js文件,View层的变化会直接影响到ViewModel层,同样ViewModel层的变化也直接反馈到View层。
因为项目最终采用了MVP的架构方式,并且基于这种架构方式,制定了一套开发规范,自研了一套开发工具,可以生成Android和iOS项目使用的Model层、View层以及Presenter层的代码,那么作为开发人员只需要关注View层代码,并且可以同时开发两端的代码,因为差别也就是在View层。
最后本文中的代码已经上传到github,可以自行下载去运行: https://github.com/wenjing-bonnie/MvcMvp.git