Android日记之MVC、MVP和MVVM
前言
在项目的设计模式中,听到最多的就是MVC、MVP和MVVM这3个架构的设计模式了,也是经常面试会问到的设计模式,本篇文章将会通过一个小demo来进行代码实战来简单的讲解这3个设计模式的区别。
demo功能分析及编写
图1我将通过3种设计模式来实现此demo的编写,demo的功能是用户输入一个账户名称,然后通过查询按钮就可以查询到用户的等级信息。我们这时候可以分析一下需求。
需求图,来源见参考,侵删
然后我们开始正式编写,首先建立一个账号的实体类和请求结果的回调接口,并且编写下布局。
//账号的实体类
public class Account {
//名字
private String name;
//账号等级
private int level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
}
//请求结果的回调接口
public interface ICallback {
void onSuccess(Account account);
void onFailed();
}
<!--布局-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询" />
<TextView
android:id="@+id/txt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test"
android:textSize="20dp" />
</LinearLayout>
接着我们就开始正式编写了,在对应的Activity下输入以下代码。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView;
private EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
initView();
}
private void initView() {
editText = findViewById(R.id.edit_text);
textView = findViewById(R.id.txt_text);
findViewById(R.id.btn_text).setOnClickListener(this);
}
@Override
public void onClick(View v) {
getAccountData(getUser(), new ICallback() {
@Override
public void onSuccess(Account account) {
showSuccessPage(account);
}
@Override
public void onFailed() {
showErrorPage();
}
});
}
//获取用户输入的信息
private String getUser(){
return editText.getText().toString();
}
//展示获取信息成功界面
private void showSuccessPage(Account account){
textView.setText("用户账号:"+ account.getName()+ "用户等级:"+ account.getLevel());
}
//展示获取信息失败界面
private void showErrorPage(){
textView.setText("获取数据失败");
}
//查询用户数据,模拟请求数据
public void getAccountData(String accountName, ICallback callback){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callback.onSuccess(account);
}else {
callback.onFailed();
}
}
}
从这段代码我们可以看出,如果不使用设计模式编写的话,发现Activity是又充当展示界面和业务逻辑,这样Activity会显的很臃肿,这时候我们就需要进行解耦了,也就是需要用到设计模式的时候了。
MVC的使用
MVC就是Model-View-Controller的缩写,即模型-视图-控制器。在Android中对应的话,可以看如图。
对应如图,来源见参考,侵删
这就是一个MVC的比较常见的模型图,箭头代表事件的传递方向,这里注意一下,Model如果传递给View,我们一般不会让Model持有View的引用,我们可以使用注册监听来实现。说了这么多,我们接下来来优化一下代码。
MVC各自负责的东西,来源见参考,侵删
刚刚说明了下MVC,在通过这个图,其实我们发现主要就是把查询账号数据的业务逻辑给分到了Model层,接着我们就创建一个Model类,把这个逻辑放进去。
public class MVCModel {
//模拟请求数据
public void getAccountData(String accountName, ICallback callback){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callback.onSuccess(account);
}else {
callback.onFailed();
}
}
}
放到这里后怎么使用呢,其实很简单,我们直接让View层持有Model层的引用就可以使用这个逻辑了。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView;
private EditText editText;
//让View层持有model层的引用
private MVCModel mvcModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
//实例化
mvcModel = new MVCModel();
}
private void initView() {
editText = findViewById(R.id.edit_text);
textView = findViewById(R.id.txt_text);
findViewById(R.id.btn_text).setOnClickListener(this);
}
@Override
public void onClick(View v) {
mvcModel.getAccountData(getUser(), new ICallback() {
@Override
public void onSuccess(Account account) {
showSuccessPage(account);
}
@Override
public void onFailed() {
showErrorPage();
}
});
}
//获取用户输入的信息
private String getUser(){
return editText.getText().toString();
}
//展示获取信息成功界面
private void showSuccessPage(Account account){
textView.setText("用户账号:"+ account.getName()+ "用户等级:"+ account.getLevel());
}
//展示获取信息失败界面
private void showErrorPage(){
textView.setText("获取数据失败");
}
}
MVC结果如图
但其实这样也是有缺点的,虽然这一定程度上实现了Model和View的分离,降低了代码的耦合性,但是其实在Android中,Activity充当了Controller和View的责任,就是Activity即是控制器,又要充当部分view层的工作,难以完全解耦,并且随着项目的提上Controller会更加的臃肿,这样子的话模型图就会变成这个样子。
模型图,来源见参考,侵删
MVP的使用
MVP就是Model-View-Presenter,在MVP中,是可以进行解耦分离的,因为在MVP中Model和View是不能直接通信的,它们只能通过Presenter来进行通信,这样子的话,Activity的功能就会被大幅度简化,不再充当控制器,主要做view层的工作就好了。
MVP模型图,来源见参考,侵删
接着我们开始写代码,首先我们这里View层的那些展示功能主要以接口的方式进行实现,Model层主要提供查询数据方面的功能,Presenter层主要负责业务逻辑的处理。
MVP各层功能,来源见参考,侵删
首先我们编写View层的接口。
public interface IMVPView {
//获取用户输入的信息
String getUser();
//展示获取信息成功界面
void showSuccessPage(Account account);
//示获取信息失败界面
void showErrorPage();
}
然后我们编写Presenter层和Model层的业务逻辑,Model的业务逻辑跟MVC是一样的。
//Model层的业务逻辑
public class MVPModel {
//模拟请求数据
public void getAccountData(String accountName, ICallback callback){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callback.onSuccess(account);
}else {
callback.onFailed();
}
}
}
//Presenter层
public class MVPPresenter {
//持有View接口的引用
private IMVPView mView;
//持有Model层的引用
private MVPModel mModel;
//通过构造函数获得实现View层接口的Activity
public MVPPresenter(IMVPView mView) {
this.mView = mView;
mModel = new MVPModel();
}
//具体的业务逻辑
public void getData(String accountName) {
mModel.getAccountData(accountName, new ICallback() {
@Override
public void onSuccess(Account account) {
mView.showSuccessPage(account);
}
@Override
public void onFailed() {
mView.showErrorPage();
}
});
}
}
刚刚也说过了View层和Model进行交互就需要通过Presenter来进行交互,就可以让Presenter层获得View层和Model的引用来进行交互,然后在写具体的业务逻辑就好啦,最后我们在View层实现View层的接口,然后通过让View层持有Presenter的引用传递进去就行,最后通过getData()
方法来获取最终的结果。
//View层的具体实现
public class MainActivity extends AppCompatActivity implements View.OnClickListener, IMVPView {
private TextView textView;
private EditText editText;
private MVPPresenter mvpPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
//View层传递给Presenter
mvpPresenter = new MVPPresenter(this);
}
private void initView() {
editText = findViewById(R.id.edit_text);
textView = findViewById(R.id.txt_text);
findViewById(R.id.btn_text).setOnClickListener(this);
}
@Override
public void onClick(View v) {
mvpPresenter.getData(getUser());
}
@Override
public String getUser() {
return editText.getText().toString();
}
@Override
public void showSuccessPage(Account account) {
textView.setText("用户账号:"+ account.getName()+ "用户等级:"+ account.getLevel());
}
@Override
public void showErrorPage() {
textView.setText("获取数据失败");
}
}
MVP结果
MVP的有点就很明显了,每个层的职责划分明显,更加易于维护,缺点也很明显,因为View主要是通过接口实现,如果项目复杂的话,接口是非常多的,而且Presenter的职责会越来越臃肿,使用MVP的建议就是接口一定要规范化,或者使用第三方插件来自动生成MVP代码。还有就是根据项目的复杂程度,部分简单的功能就没必要使用MVP模式来进行设计。
MVVM的使用
MVVM是全名Model-View-ViewModel的缩写,它跟MVP是很相似的,区别就在于Presenter替换成了ViewModel,它是在MVP的基础上实现了,数据视图的绑定(DataBinding),当数据变化时,视图也会自动更新,反之,当视图变化是,数据也会更新,优点就是减少了接口,也告别了繁琐的findViewById操作。
MVVM模型,来源见参考,侵删
使用MVVM之前,我们就需要了解DataBinding的基本用法,DataBinding是谷歌推出的实现数据绑定的框架(数据与视图的双向绑定),它可以更好的帮助我们在Android中实现MVVM。要使用DataBinding,首先我们在Build文件下输入以下代码就可以使用了。
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.ju.mvvmdemo"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//输入这段代码
dataBinding {
enabled = true
}
}
然后我们需要将布局修改为DataBinding布局。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询" />
<TextView
android:id="@+id/txt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20dp" />
</LinearLayout>
</layout>
然后我们就可以在data标签里面输入实体类了,我们把Account实体类输入进去。
<data>
<variable
name="viewModel"
type="com.ju.mvvmdemo.MVVMViewModel" />
</data>
接着我们在Activity里通过DataBindingUtil.setContentView()
去绑定视图,这里注意以下,ActivityMainBinding是在build的时候自动生成的对应绑定视图,生成的名字就是你绑定的layout的名字。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
}
然后我们创建Model层的逻辑,业务还是和之前MVP、MVC是一样的。
public class MVVMModel {
//模拟请求数据
public void getAccountData(String accountName, ICallback callback){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callback.onSuccess(account);
}else {
callback.onFailed();
}
}
}
接下来就是重点了,怎么双向绑定数据和视图呢,其实我们在layout对应的控件输入格式@{******}就可以了,我们这样输入。
<EditText
android:text="@={viewModel.userInput}"
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_text"
android:onClick="@{viewModel.getData}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询" />
<TextView
android:id="@+id/txt_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{viewModel.result}"
android:textSize="20dp" />
刚刚我们在data标签里面放入了viewModel,然后就可以在控件里面通过你想要的方法来实时更新view了,如果是view实时更新来改变数据的话(比如EditText),在@后面加一个 = 就好了。这里我们也设置了一个点击事件,当点击这个按钮后,会调用getData()
方法,这里修改完毕后我们还需要Account实体类,我们让他继承BaseObservable,然后在你需要实时更新的变量设置相应的注解。
public class Account extends BaseObservable {
private String name;
private int level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//设置注解
@Bindable
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
//通过这段代码刷新视图
notifyPropertyChanged(BR.level);
}
}
然后就是重点了,接下来编写ViewModel层的代码。
public class MVVMViewModel extends BaseObservable {
private MVVMModel mvvmModel;
private String result;
private String userInput;
//一般传入Application对象,方便在ViewModel中使用Application
//比如sharedpreferences需要使用
public MVVMViewModel(Application application) {
mvvmModel = new MVVMModel();
}
@Bindable
public String getUserInput() {
return userInput;
}
public void setUserInput(String userInput) {
this.userInput = userInput;
notifyPropertyChanged(BR.userInput);
}
@Bindable
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
notifyPropertyChanged(BR.result);
}
//点击按钮后调用这个方法,参数要View
public void getData(View view) {
mvvmModel.getAccountData(userInput, new ICallback() {
@Override
public void onSuccess(Account account) {
String info = account.getName() + "|" + account.getLevel();
setResult(info);
}
@Override
public void onFailed() {
setResult("更新失败");
}
});
}
}
最后我们在Activity中和ViewModel绑定,就可以进行使用了。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//实例化MVVMModel
MVVMViewModel mvvmViewModel = new MVVMViewModel(getApplication());
//绑定ViewModel
binding.setViewModel(mvvmViewModel);
}
}
MVVM运行结果
MVVM优点也很明显,因为数据和视图进行了双向的绑定,这样就可以极大的简化了代码,缺点就是相较于MVP和MVC使用上会复杂很多,学习成本会很大。