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使用上会复杂很多,学习成本会很大。

参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容