Android——超简单 MVC、MVP、MVVM入门系列

2022年,新年第一篇文章,本篇文章将用非常简单的言语来描述各框架,尽量让大家一看即会。

前言:

相信不少伙伴在进行Android开发的时候,肯定遇见过 Activity 代码上千行的,这种代码非常难以维护,牵一发而动全身,像极了某印#国的电线杆的电线一样,网上调侃程序员修水管,越修水越多估计也是这么来的。

而框架意在将这Activity中上千行代码进行功能分类,并提高相同功能的重复使用率,我们大体可将功能简单分为三种 界面代码、业务代码、逻辑代码。让各自专一的完成各自任务。

各自负责区域

界面代码-视图层:界面绘画、界面初始化、界面事件监听、界面数据更新

业务代码-控制层:登录需求、注册需求...

逻辑代码-模型层:需求具体的实现逻辑

接下来我们来按照 Demo无框架 -> MVC -> MVP -> MVVM 的顺序来讲解一下各自的实现与区别,本章附带源码:https://github.com/1079374315/Dome_MVC_MVP_MVVM

实现的案例:

单击按钮进行单机登录,这个例子够简单了吧,我们来看看不用框架实现时的情况。

(本代码均简易实现,不进行复杂的逻辑判断,意在将代码简化让读者更好的理解框架)

我们先看看总体结构:

公共类介绍:

实体类

数据库封装类

反馈接口

 

Demo无框架

public class Demo_Activity extends AppCompatActivity implements View.OnClickListener {

    private TextView tv_showData;
    private EditText et_userName;
    private EditText et_passWord;
    private SQLUtils sqlUtils;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();//初始化UI组件
        sqlUtils = new SQLUtils();//初始化数据库操作类
    }

    private void initView() {
        tv_showData = findViewById(R.id.tv_showData);
        et_userName = findViewById(R.id.et_userName);
        et_passWord = findViewById(R.id.et_passWord);
        findViewById(R.id.btn_login).setOnClickListener(this);//注册单击事件
    }

    @Override
    public void onClick(View v) {
        //单击进行登录
        loginRequest(getLoginData(), new OnLoginCallback() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onSuccess(LoginBean loginBean) {
                tv_showData.setText("登录成功:" + loginBean.toString());
            }

            @Override
            public void onError() {
                tv_showData.setText("登录失败,账号密码错误!");
            }
        });
    }

    //获取登录数据
    private LoginBean getLoginData() {
        String userName = et_userName.getText().toString();
        String passWord = et_passWord.getText().toString();
        return new LoginBean(userName, passWord);
    }

    //登录请求,将数据与数据库的数据进行对比
    private void loginRequest(LoginBean loginBean, OnLoginCallback loginCallback) {
        if (sqlUtils.queryUserName().equals(loginBean.getUserName()) &&
                sqlUtils.queryPassWord().equals(loginBean.getPassWord())) {
            //登录成功
            loginCallback.onSuccess(loginBean);//调用接口反馈成功数据
        } else {
            //登录失败
            loginCallback.onError();//调用接口反馈失败数据
        }
    }

}

用以上代码就可以实现一个登录实例,我们将这个代码图解一下:

界面代码-视图层:界面绘画、界面初始化、界面事件监听、界面数据更新

业务代码-控制层:登录需求、注册需求...

逻辑代码-模型层:需求具体的实现逻辑

 目前我们可以看到 界面代码、业务代码、逻辑代码 这些代码均在同一个界面,当我们这个界面功能变多了,那这个Activity将会越来越臃肿,变得难以维护。

现在我们用 MVC 来看看如何把他进行解耦优化。

MVC模式:

(先解释一下箭头)

View -> Controller 表示:Controller持有 View类的引用,单向则表示,Controller可调用View类中方法,但View无法调用Contriller, 后面的Controller -> Model 也一样。

界面代码-视图层-View:界面绘画、界面初始化、界面事件监听、界面数据更新

业务代码-控制层-Controller:登录需求、注册需求...

逻辑代码-模型层-Model:需求具体的实现逻辑

我们先看看总体结构:

 公共类还是用之前的,MVC就新增了两个类,Activity 与 Model

MVC_Model

/**
 * author:hello
 * time:2020/7/31
 * CSDN: qq_39799899
 * explain:其实就是将与UI无关的事情移至到 Mvc_Model中
 * 如:网络请求、数据获取、查询等
 **/
public class MVC_Model {

    private SQLUtils sqlUtils;

    public MVC_Model(){
        sqlUtils = new SQLUtils();//初始化数据库封装类
    }

    //登录请求,将数据与数据库的数据进行对比
    public void loginRequest(LoginBean loginBean, OnLoginCallback loginCallback) {
        if (sqlUtils.queryUserName().equals(loginBean.getUserName()) &&
                sqlUtils.queryPassWord().equals(loginBean.getPassWord())) {
            //登录成功
            loginCallback.onSuccess(loginBean);
        } else {
            //登录失败
            loginCallback.onError();
        }
    }

}

MVC_Activity

public class MVC_Activity extends AppCompatActivity implements View.OnClickListener {
    private TextView tv_showData;
    private EditText et_userName;
    private EditText et_passWord;
    private MVC_Model mvc_model;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();
        mvc_model = new MVC_Model();//初始化Model
    }
    private void initView() {
        tv_showData = findViewById(R.id.tv_showData);
        et_userName = findViewById(R.id.et_userName);
        et_passWord = findViewById(R.id.et_passWord);
        findViewById(R.id.btn_login).setOnClickListener(this);//注册事件
    }
    @Override
    public void onClick(View v) {
        mvc_model.loginRequest(getLoginData(), new OnLoginCallback() {//业务代码
            @Override
            public void onSuccess(LoginBean loginBean) {
                tv_showData.setText("登录成功:" + loginBean.toString());//设置数据
            }
            @Override
            public void onError() {
                tv_showData.setText("登录失败,账号密码错误!");//设置数据
            }
        });
    }
    //获取登录数据
    public LoginBean getLoginData() {
        String userName = et_userName.getText().toString();
        String passWord = et_passWord.getText().toString();
        return new LoginBean(userName, passWord);
    }
}

 我们MVC图解一下:

界面代码-视图层:界面绘画、界面初始化、界面事件监听、界面数据更新

业务代码-控制层:登录需求、注册需求...

逻辑代码-模型层:需求具体的实现逻辑

 在MVC的体系上是不是发现,逻辑层代码已经被搬离出去了,这里就做到了逻辑代码的解耦,接下来我们再分析一下逻辑层MVC_Model里的代码

 只需要调用者提供数据源与反馈数据接口,不管是那个Activity,都可以继续重用该业务逻辑代码,这样是不是一下就提高了代码的重复利用性,降低了代码的耦合性,而且今后改逻辑也只需要改一处就好。

我们再来看看项目中自带的MVC文档

 MVC缺点也确实很明显,逻辑代码确实已经被搬离出去了,但控制层的代码的代码依旧还在Activity中,接下来我们来看看如何将这控制层的代码也搬离出去,进行更优的解耦。

MVP模式:

 

(先解释一下箭头)

View →← Presenter表示:Presenter与View类 相互持有彼此的引用,Controller可调用View类中方法,且View同样也可调用Presenter方法, 后面的Controller -> Model 也一样。

Presenter意在将MVC中 View 持有Model引用给完全隔离掉,View 与 Model之间交互完全由Presenter来帮助与协调。

            MVC示意图

我们先看看总体结构

 公共类上面已介绍过了,我们继续来看看MVP这些接口

Model 与 上面MVC里介绍的Model 没有变化

public class MVP_Model {
    private SQLUtils sqlUtils;
    public MVP_Model(){
        sqlUtils = new SQLUtils();
    }
    //登录请求
    public void loginRequest(LoginBean loginBean, OnLoginCallback loginCallback) {
        if (sqlUtils.queryUserName().equals(loginBean.getUserName()) &&
                sqlUtils.queryPassWord().equals(loginBean.getPassWord())) {
            //登录成功
            loginCallback.onSuccess(loginBean);
        } else {
            //登录失败
            loginCallback.onError();
        }
    }
}

 MVP_View

public interface MVP_View extends OnLoginCallback{

    LoginBean getLoginData();

}

 接下来我们来看看Presenter如何来协调 MVP_View 与 MVP_Model 的

public class MVP_Presenter {
    private MVP_View mvp_view;
    private MVP_Model mvp_model;

    public MVP_Presenter(MVP_View mvp_view) {
        this.mvp_view = mvp_view;//view接口由Activity那边传递
        this.mvp_model = new MVP_Model();//model在Presenter内创建
    }

    public void loginRequest(LoginBean loginBean) {
        mvp_model.loginRequest(loginBean, new OnLoginCallback() {
            public void onSuccess(LoginBean loginBean1) {
                mvp_view.onSuccess(loginBean1);//反馈成功数据
            }

            public void onError() {
                mvp_view.onError();//反馈失败
            }
        });
    }
}

 我们来看看图解:

 接下来我们来看看 业务代码与逻辑代码都被解耦了,那Activity还有什么事呢?

//实现 MVP_View 接口重写 getLoginData、onSuccess、onError、进行获取数据与数据更新
public class MVP_Activity extends AppCompatActivity 
implements View.OnClickListener, MVP_View {
    private TextView tv_showData;
    public EditText et_userName;
    public EditText et_passWord;
    private MVP_Presenter mvp_presenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();
        mvp_presenter = new MVP_Presenter(this);
    }
    private void initView() {
        tv_showData = findViewById(R.id.tv_showData);
        et_userName = findViewById(R.id.et_userName);
        et_passWord = findViewById(R.id.et_passWord);
        findViewById(R.id.btn_login).setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
        mvp_presenter.loginRequest(getLoginData());//请求登录
    }
    @Override
    public LoginBean getLoginData() {
        String userName = et_userName.getText().toString();
        String passWord = et_passWord.getText().toString();
        return new LoginBean(userName, passWord);
    }
    @SuppressLint("SetTextI18n")
    @Override
    public void onSuccess(LoginBean MVVMLoginBean) {
        tv_showData.setText("登录成功:" + MVVMLoginBean.toString());
    }

    @Override
    public void onError() {
        tv_showData.setText("登录失败,账号密码错误!");
    }
}

这张图解是不是发现大多数都是界面代码没发现有逻辑代码了,因为逻辑代码已经被解耦到 Presenter里去了,View没有Model的引用了。

而且你细心一点就会发觉,MVP_View 这个接口也是可以多次实现的,也就是说,目前的Presenter也是可以复用,Model也是可以复用的。

界面代码-视图层:界面绘画、界面初始化、界面事件监听、界面数据更新

业务代码-控制层:登录需求、注册需求...

逻辑代码-模型层:需求具体的实现逻辑

               

                MVP示意图 

MVP不愧是MVC的升级版,更加提升了框架代码的复用性

我们来看看MVP的文档

 

 其实MVP已经很优解了,解耦性也比较不错了,但我们还有更优解的 MVVM模式且不用写那么多接口,代码量也更少

MVVM模式:

 (先解释一下箭头)

View ↔ ViewModel表示:View与ViewModel相互绑定,然后ViewModel与Model又相互持有彼此引用,相互持有引用相信大家已经非常熟悉了,但这相互绑定是怎样的,没了解过的伙伴估计不太理解。

相互绑定的话,View可以通过 ViewModel 从 Model 中获取数据;当获取到了数据之后,会通过自动绑定相互绑定的作用,设置到View界面展示。

相互绑定的作用:可以省去 组件获取、事件注册、数据获取、数据更新这些代码。我们来看看之前MVP中Activity代码,除去Presenter代码Model代码,View代码里剩下的全是组件获取、事件注册、数据获取、数据更新了,而MVVM就是省去这些代码。

                                                        MVP Activity代码

我们先看看总体结构:

 想要实现MVVM就得 打开DataBindingUtil :找到.app

在android{}下添加开启:

 然后我们再来看看MVVM 的 Model,铁打的Model 流水的View, Model层与MVC、MVP的Model一样。

Model

public class MVVM_Model {
    private SQLUtils sqlUtils;
    public MVVM_Model(){
        sqlUtils = new SQLUtils();
    }
    //登录请求
    public void loginRequest(LoginBean loginBean, OnLoginCallback loginCallback) {
        if (sqlUtils.queryUserName().equals(loginBean.getUserName()) &&
                sqlUtils.queryPassWord().equals(loginBean.getPassWord())) {
            //登录成功
            loginCallback.onSuccess(loginBean);
        } else {
            //登录失败
            loginCallback.onError();
        }
    }
}

 我们再来看看 ViewModel 是如何与View互相绑定的

ViewModel

public class MVVM_ViewModel extends BaseObservable {
    private MVVM_Model mvvm_model;
    private String result;
    private ActivityMvvmBinding binding;
    //该方法由View界面自动调用
    @Bindable
    public String getResult() {
        return result;
    }
    //设置结果到View界面并刷新
    public void setResult(String result) {
        this.result = result;
        notifyPropertyChanged(BR.result);
    }
    //构造方法初始化
    public MVVM_ViewModel(ActivityMvvmBinding binding) {
        mvvm_model = new MVVM_Model();
        this.binding = binding;
    }
    //通过binding获取数据
    public LoginBean getLoginBean() {
        return new LoginBean(
                binding.etUserName.getText().toString()
                , binding.etPassWord.getText().toString()
        );
    }
    //View界面注册的单击事件
    public void loginRequest(View view) {
        mvvm_model.loginRequest(getLoginBean(), new OnLoginCallback() {
            @Override
            public void onSuccess(LoginBean loginBean) {
                setResult("获取成功:" + loginBean.toString());
            }
            @Override
            public void onError() {
                setResult("获取失败:账号或密码错误!");
            }
        });
    }
}

 我们来图解一下:图有点大,但放大看还是看的很透彻的

 看完这张图你应该可以明白,View与ViewModel是如何绑定的了吧。

那我们再来看看此时的Activity还负责那些代码

 仅负责 View 与 ViewModel 之间的绑定后,就做甩手掌柜了。

现在是不是感觉代码少了很多很多。

小伙伴门可以将Model 静态化试试,将会有新的发现。

博主将 MVC MVP MVVM 的辅助方法加入了GT库中,在MVVM的基础上更优解,感兴趣的小伙伴们可 下载MVVM-GT版 进行查看

本章演示源码:GitHub - 1079374315/Dome_MVC_MVP_MVVM

MVVM-GT库版源码:GitHub - 1079374315/MVC_MVP_MVVM_GT

来源url
栏目