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

2019-11-22  本文已影响0人  大亮亮亮亮
前言

不管是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 + 双向绑定
上一篇下一篇

猜你喜欢

热点阅读