一个简单的例子描述了MVC、MVP和MVVM之间的关系

用一个小栗子描述MVC、MVP和MVVM之间的关系
在这里插入图片描述
项目结构:
在这里插入图片描述
这是一个非常简单的例子,点击按钮,查询现在广州的天气信息。

MVC

什么是MVC?(来自百度百科的回答)
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

在Android中,Model、View和Controller的描述:

  • 模型层 Model
    针对业务模型,建立的数据结构和相关的类,提供接口操作数据和处理业务逻辑。
  • 视图层 View
    显示数据和用户交互的界面,一般采用xml文件进行界面描述,可以理解为Android APP中的View,Activity或者Fragment。
  • 控制层 Controller
    作为Model和View之间的桥梁,来控制View层和Model层之间的通讯,从而达到分离视图显示和业务逻辑层。但是通常这部分都写在了Activity之上,所以Activity里面的代码会非常臃肿。
    在这里插入图片描述

当View需要更新时,首先去找Controller,然后Controller去找Model获取数据,Model获取数据之后,通知View更新UI。

例子说明
在这里插入图片描述
先定义一个获取天气预报的接口

public interface IWeatherModel {
    void getWeather(OnWeatherListener onWeatherListener);
}

需要定义个接口监听天气信息是否获取成功

public interface OnWeatherListener
{
    void onSuccess(WeatherBean weather);
    void onError();
}

实现获取天气预报接口

public class WeatherModelImpl implements IWeatherModel
{
    @Override
    public void getWeather(final OnWeatherListener onWeatherListener) {
        String url = "http://www.weather.com.cn/data/sk/101280101.html";
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                onWeatherListener.onError();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String body = response.body().string();
                Gson gson = new Gson();
                WeatherBean weather = gson.fromJson(body, WeatherBean.class);
                onWeatherListener.onSuccess(weather);
            }
        });

    }
}

到这里,Model层已经写好了,提供了getWeather()方法获取天气信息。
View层

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".mvc.MvcActivity">

    <TextView
        android:id="@+id/tv_weather_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:textSize="28sp"
        />

    <Button
        android:id="@+id/bt_query"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:layout_marginTop="10dp"
        app:layout_constraintTop_toBottomOf="@id/tv_weather_info"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:text="@string/bt_query_text"
        />


</android.support.constraint.ConstraintLayout>

Controller层, 这里的Controller就是weatherModel.getWeather(this)

@OnClick(R.id.bt_query)
    public void onQueryClick() {
        weatherModel.getWeather(this);
    }

View & Controller 全部代码

package com.act.androidarchtest.mvc;


import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import android.widget.Toast;

import com.act.androidarchtest.R;
import com.act.androidarchtest.base.BaseActivity;
import com.act.androidarchtest.bean.WeatherBean;
import com.act.androidarchtest.mvc.model.IWeatherModel;
import com.act.androidarchtest.mvc.model.OnWeatherListener;
import com.act.androidarchtest.mvc.model.WeatherModelImpl;

import butterknife.BindView;
import butterknife.OnClick;

public class MvcActivity extends BaseActivity implements OnWeatherListener {

    @BindView(R.id.tv_weather_info)
    TextView mTvWeatherInfo;

    private static final int QUERY_WEATHER_SUCCESS = 1;
    private static final int QUERY_WEATHER_FAIL = 0;

    private IWeatherModel weatherModel;
    private WeatherBean weather;
    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case QUERY_WEATHER_FAIL:
                    Toast.makeText(getApplicationContext(), "获取天气信息失败", Toast.LENGTH_SHORT).show();
                    break;
                case QUERY_WEATHER_SUCCESS:
                    mTvWeatherInfo.setText(weather.toString());
                    break;
            }
            return false;
        }
    });


    @Override
    protected void initActivity() {
        weatherModel = new WeatherModelImpl();
    }


    @OnClick(R.id.bt_query)
    public void onQueryClick() {
        weatherModel.getWeather(this);
    }


    @Override
    public void onSuccess(WeatherBean weather) {
        this.weather = weather;
        mHandler.sendEmptyMessage(QUERY_WEATHER_SUCCESS);
    }

    @Override
    public void onError() {
        mHandler.sendEmptyMessage(QUERY_WEATHER_FAIL);
    }

    @Override
    protected int bindActivityLayout() {
        return R.layout.activity_mvc;
    }

}

当用户点击查询按钮,这里的Activity既作为Controller去找到WeatherModel,并调用它的getWeather()方法去获取天气信息,这里的Activity又作为View,当WeatherModel处理完成之后,通过接口OnWeatherListener通知View视图数据获取成功,并更新数据显示。

扫描二维码关注公众号,回复: 11193825 查看本文章

到这里,整个MVC架构就可以体现出来了。

MVP

为什么要用MVP呢?
从上面的Activity代码中可以看出,Model层可以直接更新View层,而且Activity既充当了View层,又充当了Controller层,如果Controller层业务变得越来越多,那么Activity引入的代码量是比较大的,后期难于维护,因此引入了MVP架构,其实MVP更是一种思想,完全解耦Model层与View层之间的联系。
所谓MVP就是 Model、View和Presenter。注意这里引入了Presenter。

  • Model
    跟MVC的中的Model一样,负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)
  • View
    跟MVC中的View一样,负责界面显示和用户交互
  • Presenter
    作为View与Model交互的中间纽带,处理用户交互带来的业务。
    在这里插入图片描述

当View需要更新数据时,首先去找Presenter,然后Presenter去找Model请求数据,Model获取数据之后通知Presenter,Presenter再通知View更新数据。这样Model和View就不会直接交互了,所有的交互由Presenter进行,Presenter充当桥梁角色。因此,Presenter必须持有View和Model对象的引用,才能在这两者之间进行通信。

将上面的例子改成MVP架构
改一下IWeatherModel接口,取消getWeather()方法里面的参数,增加一个onCancel()取消方法。

public interface IWeatherModel {
    void getWeather();
    void onCancel();
}

改一下IWeatherModel接口实现类WeatherModelImpl

public class WeatherModelImpl implements IWeatherModel
{
    private static final int QUERY_WEATHER_SUCCESS = 1;
    private static final int QUERY_WEATHER_FAIL = 0;

    private OnWeatherListener onWeatherListener;

    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (null == onWeatherListener) return false;
            switch (msg.what) {

                case QUERY_WEATHER_SUCCESS:
                    WeatherBean weather = (WeatherBean) msg.obj;
                    onWeatherListener.onSuccess(weather);
                    break;

                case QUERY_WEATHER_FAIL:
                    onWeatherListener.onError();
                    break;
            }
            return false;
        }
    });

    public WeatherModelImpl(OnWeatherListener onWeatherListener) {
        this.onWeatherListener = onWeatherListener;
    }

    @Override
    public void getWeather() {
        String url = "http://www.weather.com.cn/data/sk/101280101.html";
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                Message message = new Message();
                message.what = QUERY_WEATHER_FAIL;
                mHandler.sendMessage(message);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String body = response.body().string();
                Gson gson = new Gson();
                WeatherBean weather = gson.fromJson(body, WeatherBean.class);
                Message message = new Message();
                message.what = QUERY_WEATHER_SUCCESS;
                message.obj = weather;
                mHandler.sendMessage(message);
            }
        });

    }

    @Override
    public void onCancel() {
        mHandler.removeMessages(mHandler.obtainMessage().what);
    }
}

使用OnWeatherListener接口回调,如果获取天气信息成功,回调onWeatherListener.onSuccess(weather),请求失败回调onWeatherListener.onError();实现onCancel(),取消Handler里面的任务,防止Activity被回收掉了,请求数据将变得没有任何意义。

创建一个IWeatherView接口,用来更新View上的天气信息

public interface IWeatherView {
    void updateWeather(WeatherBean weatherBean);
    void updateError();
}

创建一个Presenter,作为IWeatherView 和WeatherModelImpl之间通信的桥梁

public class WeatherPresenter {

    private IWeatherModel weatherModel;
    private IWeatherView weatherView;

    public WeatherPresenter(IWeatherView weatherView) {
        this.weatherView = weatherView;
        weatherModel = new WeatherModelImpl(new OnWeatherListener() {
            @Override
            public void onSuccess(WeatherBean weather) {
                WeatherPresenter.this.weatherView.updateWeather(weather);
            }

            @Override
            public void onError() {
                WeatherPresenter.this.weatherView.updateError();
            }
        });
    }

    public void getWeather() {
        weatherModel.getWeather();
    }

    public void detachedView() {
        weatherView = null;
        weatherModel.onCancel();
    }

}

从这里就可以看出来,当外部调用weatherPresenter.getWeather()时,WeatherPresenter就会去找weatherModel,就是调用它的getWeather()方法,当weatherModel获取数据之后,通知weatherPresenter,由weatherPresenter去通知weatherView进行UI更新,就是调用weatherView.updateWeather(weather)

在这里增加了detachView(),防止Activity被回收的时候,Presenter还持有它的引用,防止内存泄露。

View层

public class MvpActivity extends BaseActivity implements IWeatherView {


    @BindView(R.id.tv_weather_info)
    TextView mTvWeatherInfo;

    private WeatherPresenter weatherPresenter;

    @Override
    protected void initActivity() {
        weatherPresenter = new WeatherPresenter(this);
    }

    @OnClick(R.id.bt_query)
    public void onQuery() {
        weatherPresenter.getWeather();
    }

    @Override
    public void updateWeather(WeatherBean weatherBean) {
        mTvWeatherInfo.setText(weatherBean.toString());
    }

    @Override
    public void updateError() {
        Toast.makeText(this, "获取天气信息失败", Toast.LENGTH_SHORT).show();
    }

    @Override
    protected int bindActivityLayout() {
        return R.layout.activity_mvp;
    }

    @Override
    protected void onDestroy() {
        weatherPresenter.detachedView();
        super.onDestroy();
    }

上面的Activity实现了IWeatherView接口,Presenter就可以通过调用IWeatherView接口更新UI,并且持有这个View的引用。

View本身只用于数据显示,并不处理任何数据业务。在这里View不再依赖于Model,View不参与决策,View与Model不能直接交互,Presenter是整个MVP体系的控制中心。View通过Presenter与Model打交道。Presenter接受View的UI请求,完成简单的UI处理逻辑,并调用Model进行业务处理,并调用View将相应的结果反映出来,这就是MVP架构思想。

MVVM

其实MVVM也是一种思想,是MVP的升级版,其中VM指的是ViewModel。实现MVVM也有很多种方法,下面我用的是比较普遍的DataBinding方式。DataBinding可以实现双向的交互,这就使得视图和控制层之间的耦合程度进一步降低,关注点分离更为彻底,同时减轻了Activity的压力。

官方是支持MVVM模式框架的,可以在xml中直接绑定数据,无需再用ButterKnife或者findViewById手动设置数据,实现UI与功能的解耦。

用MVVM实现上面的例子
首先在app/build.gradle配置

android {
   ...
   dataBinding {
        enabled = true
    }
}

在原来的layout文件上面添加layout标签,把原来的布局放到里面。注意是小写的layout哦!不然使用data标签会报错的。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <import type="android.view.View"/>
        <variable
            name = "weatherModel" type = "com.act.androidarchtest.mvvm.model.WeatherModelImpl"/>
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_weather_info"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="@{weatherModel.querySuccess ? View.VISIBLE : View.GONE}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:textSize="28sp"
            android:text="@{weatherModel.weatherInfo}"
            />

        <Button
            android:id="@+id/bt_query"
            android:layout_width="150dp"
            android:layout_height="50dp"
            android:layout_marginTop="10dp"
            app:layout_constraintTop_toBottomOf="@id/tv_weather_info"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:text="@string/bt_query_text"
            android:onClick="@{() -> weatherModel.getWeather()}"
            />

    </android.support.constraint.ConstraintLayout>



</layout>

修改WeatherModelImpl类,取消原来的接口回调,使用观察者模式。

public class WeatherModelImpl implements IWeatherModel
{

    public final ObservableBoolean querySuccess = new ObservableBoolean(false);

    public final ObservableField<String> weatherInfo = new ObservableField<>();

    private Call mCall;

    public WeatherModelImpl() { }

    @Override
    public void getWeather() {
        String url = "http://www.weather.com.cn/data/sk/101280101.html";
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder()
                .url(url)
                .get()
                .build();
        mCall = okHttpClient.newCall(request);
        mCall.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                querySuccess.set(false);
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                String body = response.body().string();
                Gson gson = new Gson();
                WeatherBean weather = gson.fromJson(body, WeatherBean.class);
                querySuccess.set(true);
                weatherInfo.set(weather.toString());
            }
        });

    }

    @Override
    public void onCancel() {
        if (null != mCall) {
            mCall.cancel();
        }
    }
}

由于之前的layout文件已经直接绑定了weatherModel.weatherInfo数据,所以一旦weatherInfo数据发生变化,绑定数据的View视图则会自动刷新UI。
View层代码

public class MvvmActivity extends AppCompatActivity {

    private WeatherModelImpl weatherModel;

    private ActivityMvvmBinding mMvvmBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMvvmBinding = DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
        weatherModel = new WeatherModelImpl();
        mMvvmBinding.setWeatherModel(weatherModel);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        weatherModel.onCancel();
    }
}

通过上面代码可以看出,用了MVVM之后,Activity整体的代码量少了很多,不用处理View刷新问题。

上面例子的全部代码 GitHub传送门

参考文章:https://www.cnblogs.com/wytiger/p/5305087.html

原创文章 19 获赞 26 访问量 9217

猜你喜欢

转载自blog.csdn.net/qq_36270361/article/details/105383417
今日推荐