Android中的MVC、MVP、MVVM架构新的思考

前言

不管是iOS开发、Android开发、甚至是游戏开发,MVX都是说到烂而且被普遍大众接受的设计框架,可这个框架一直以来都有很多误区。写此文的目的是方便自己整理一下MVX的思路,也提供大家思考。

架构介绍

常用的设计架构就几个,MVC、MVP、MVVM。软件开发的套路无非就几个步骤,画界面拿数据更新界面。而在这个过程中程序员为了不把所有的操作都塞到一个类文件去写代码,引申出MVX的架构设计。

目的:解耦

1、MVC模式
image.png

Model:数据的获取、操作
更新:数据更新后,反馈给UI显示通知View更新数据

View:看得见的东西
用户事件:通知Controller点击了按钮

Controller:负责数据处理
数据操作:通知Model进行数据操作,比如从网络上获取数据

传统意义上的MVC模式应该是上述的部分的描述,但在移动开发中我们会对MVC产生了很多误解。因为在iOS和Android这样的框架下面开发,我们基本只需要关心ViewController/Activity的实现就可以。

大众观点认为:
View:XML布局文件;Model:数据的操作类;Controller:处理数据、业务和UI。更像图下的结构

image.png

我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。

代码例子:
public class MVCActivity extends AppCompatActivity {

    EditText editText1;
    EditText editText2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        editText1 = findViewById(R.id.data1View);
        editText2 = findViewById(R.id.data2View);

        //数据操作
        String[] data = Model.getData();

        //UI更新
        editText1.setText(data[0]);
        editText2.setText(data[1]);
    }

}

一般来说,为了解决这个问题我们就开始引入MVP模式了.....等一下!!!

既然MVP模式是为了把业务逻辑操作抽离出去作为一个Presenter,那我可以不可以把各种findViewById和setText的东西抽离出去呢?

所以MVC模式又变成了:
Model:没变
View:xml布局+新建View类

public class MVCView extends LinearLayout implements MVCActivity.IView{
    EditText editText1;
    EditText editText2;
    Button button;

    public MVCView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        editText1 = findViewById(R.id.data1View);
        editText2 = findViewById(R.id.data2View);
        button = findViewById(R.id.saveButton);

    }

    @Override
    public void showData(String[] data){
        //更新
        editText1.setText(data[0]);
        editText2.setText(data[1]);
    }

    @Override
    public void listenButton(OnClickListener listener) {
        button.setOnClickListener(listener);
    }

}

继承自LinerLayout,因为原来的布局是使用LinerLayout,注意xml的根节点就变成我新建的View类了~

<c.damon.makemyprogress.mvx.mvc.MVCView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mvc_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/data1View"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <EditText
        android:id="@+id/data2View"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/saveButton"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:text="Save"/>

</c.damon.makemyprogress.mvx.mvc.MVCView>

Controller:activity

public class MVCActivity extends AppCompatActivity {

    interface IView {
        void showData(String[] data);
        void listenButton(View.OnClickListener listener);
    }

    IView mView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvc);
        mView = findViewById(R.id.mvc_view);

        //数据操作
        String[] data = Model.getData();

        //UI操作
        mView.showData(data);
        mView.listenButton(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("123123123");
            }
        });
    }
}

读到这里,相信你已经可以感受到这种写法可以把大部分的UI逻辑解耦出去了。

2、MVP
image.png

Model: 数据的获取、操作。
View: 对应于Activity和XML,负责View的绘制以及与用户的交互。
Presenter: 负责完成View与Model间的交互和业务逻辑。

代码例子:

Model:没变
View:Activtiy+xml

public class MVPActivity extends AppCompatActivity implements Presenter.IView{

    EditText editText1;
    EditText editText2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvp);
        editText1 = findViewById(R.id.data1View);
        editText2 = findViewById(R.id.data2View);

        new Presenter(this).load();
    }


    @Override
    public void showData(String[] data) {
        editText1.setText(data[0]);
        editText2.setText(data[1]);
    }
}

Presenter:新建一个Presenter类

public class Presenter {

    interface IView {
        void showData(String[] data);
    }

    IView mView;

    Presenter(IView mView){
        this.mView = mView;
    }

    public void load() {
        //数据操作
        String[] data = Model.getData();
        mView.showData(data);
    }

}

从思想上来讲,MVP跟MVC竟然是没区别的,唯一的区别是结构上的,一个是把界面逻辑拆出去,另外一个是把业务逻辑拆出去。

在MVP中,V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的IView接口就必须得改,并且IView接口方法太多也太臃肿,如果这一层也能解耦就更好了。

MVVM
image.png

Model: 数据的获取、操作。
View: 对应于Activity和XML,负责View的绘制以及与用户的交互。
ViewModel: 负责完成View与Model间的交互和业务逻辑。

思想竟然又是一样的,所以 MVVM = MVP + 双向绑定工具

数据的双向绑定:通俗点的理解是表象数据与内存数据,不管哪个发生变化,另外一个也同时发生变化。比如我有一个EditTextView,在我设置一些值之后,与之对应的数据模型也同时发生变化。

好神奇的功能,相当于把MPV中的showData()方法省掉了。具体怎么实现呢?

代码例子(伪代码):

Model:没变
View:Activtiy+xml

public class MVVMActivity extends AppCompatActivity {

    EditText editText1;
    EditText editText2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvvm);
        editText1 = findViewById(R.id.data1View);
        editText2 = findViewById(R.id.data2View);

        new ViewModel(new ViewBinder(),editText1,editText2).load();

    }

}

ViewModel:新建一个ViewModel类

public class ViewModel {

    String datum0, datum1;
    
    ViewModel(ViewBinder binder, EditText editText0, EditText editText1) {
        binder.bind(editText0, datum0);
        binder.bind(editText1, datum1);
    }

    public void load() {
        //数据操作
        String[] data = Model.getData();
        datum0.setValue(data[0]);
        datum1.setValue(data[1]);
    }
}

ViewBinder工具类(伪代码):

public class ViewBinder {
    void bind(final EditText editText, final String datum) {
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void afterTextChanged(Editable s) {
                if (!Objects.equals(s.toString(), datum.getValue())) {
                    datum.setValue(s.toString());
                }
            }
        });
        datum.setOnChangeListener(new OnChangeListener() {
            @Override
            public void onChange(String newValue) {
                if (!Objects.equals(newValue, editText.getText().toString())) {
                    editText.setText(newValue);
                }
            }
        });
    }
}

ViewBinder的本质是一个监听器,内部通过互相监听的方式去实现数据的双向绑定,而DataBinding这个库已经帮我们做好ViewBinder所做的事情。

修改一下Activity类

public class MVVMActivity extends AppCompatActivity {

    EditText editText1;
    EditText editText2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mvvm);
        editText1 = findViewById(R.id.data1View);
        editText2 = findViewById(R.id.data2View);

        ActivityMvvmBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        binding.setHandlers(new Handler());

        new ViewModel(binding).load();
        //new ViewModel(new ViewBinder(),editText1,editText2).load();

    }


}

修改一下ViewModel类

public class ViewModel {

    ActivityMvvmBinding binding;

    public ViewModel(ActivityMvvmBinding binding) {
        this.binding = binding;
    }

    public void load() {
        String[] strs = Model.getData();
        binding.setDm(new DataModel(strs[0], strs[1]));
    }

}

此处就不介绍DataBinding的用法了~大家可以参考其它资料

总结

我们重新总结一下MVC、MVP、MVVM中的职责

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

推荐阅读更多精彩内容