一個簡單的例子描述了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視圖數據獲取成功,並更新數據顯示。

到這裏,整個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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章