android MVC && MVP && MVVM分析和對比

Shawn_Dut發表於2017-04-17

  面試的時候被問到這個問題,用過,也瞭解過,但是還是不夠深入,總結一下。
  MVC,MVP和MVVM是軟體比較常用的三種軟體架構,這三種架構的目的都是分離關注,避免將過多的邏輯全部堆積在一個類中,以android為例,在activity中既有UI的相關處理邏輯,又有資料獲取邏輯,從而導致activity邏輯複雜不單一難以維護。為了一個應用可以更好的維護和擴充套件,我們需要很好的區分相關層級,要不然以後將資料獲取方式從資料庫變為網路獲取時,我們需要去修改整個activity。架構使得view和資料相互獨立,我們把應用分成三個不同層級,這樣我們就能夠單獨測試相關層級,使用架構能夠把大多數邏輯從activity中移除,方便進行單元測試。

MVC

  Model View Controller模式,MVC將應用分成三個主要層級:Model,View和Controller,它強制將邏輯進行分離,資料結構和Controller邏輯與UI是解耦的,所以測試相關模組變的更簡單。
  

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

  其實android app的介面開發部分已經是遵從MVC模式的,

  • 檢視層(View):一般採用XML檔案進行介面的描述,使用的時候可以非常方便的引入,當然,也可以使用JavaScript+HTML等的方式作為View層,他的職責就是負責顯示從Controller上獲取到的資料(但是xml佈局作為View來說功能很無力,所以通常Activity也會承擔一部分View的工作)。
  • 控制層(Controller):Android的控制層的重任通常落在了眾多的Activity的肩上,他們從模型層獲取資料,將獲取到的資料繫結到view上,並且還需要監聽使用者的輸入等操作。
  • 模型層(Model):對資料庫的操作、對網路等的操作都應該在Model裡面處理,當然對業務計算,變更等操作也是必須放在的該層的。
MVC模式具體表現在android上的效果如下圖所示:
android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

  也可以看看這個視訊,介紹的不錯:
  
android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

還有另外的,android 中的adapter也是使用的MVC模式,自定義的adapter相當於Controller。

例子

  以一個獲取天氣的例子來說,xml佈局可視為View層;Activity為Controller層,控制使用者輸入,將Model層獲取到的資料展示到View層;Model層的實體類當然就是用來獲取網路資料了。
  Model層
  WeatherModel.class介面

public interface WeatherModel {
    void getWeather(OnLoadWeatherCallback callback);
}複製程式碼

  WeatherModelImpl.class類

public class WeatherModelImpl implements WeatherModel{

    private Context mContext;

    public WeatherModelImpl(Context context){
        mContext = context;
    }

    @Override
    public void getWeather(final OnLoadWeatherCallback callback) {
        NetApi.getInstance().jsonObjectRequest(mContext, "http://www.weather.com.cn/data/sk/101010100.html",
                new HashMap<String, String>(), new BaseNetApi.OnNetCallback<JSONObject>() {

            @Override
            public void onSuccess(JSONObject jsonObject) {
                try {
                    jsonObject = new JSONObject(jsonObject.getString("weatherinfo"));
                    WeatherInfo info = new WeatherInfo();
                    info.city = jsonObject.getString("city");
                    info.temp = Double.parseDouble(jsonObject.getString("temp"));
                    info.WD = jsonObject.getString("WD");
                    info.WS = jsonObject.getString("WS");
                    info.time = jsonObject.getString("time");
                    callback.onLoadSuccess(info);
                } catch (JSONException e) {
                    L.e(e);
                }
            }

            @Override
            public void onFail(NetError netError) {
                callback.onError(netError);
            }
        });
    }
}複製程式碼

  Controller層
  WeatherActivity.class類

public class WeatherActivity extends BaseActivity implements OnLoadWeatherCallback{
    private TextView tv_name;
    private TextView tv_temperature;
    private TextView tv_wind_d;
    private TextView tv_wind_s;
    private TextView tv_time;
    private LoadingDialog ld;
    private WeatherModel weatherModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_weather);
        tv_name = (TextView) findViewById(R.id.tv_name);
        tv_temperature = (TextView) findViewById(R.id.tv_temperature);
        tv_wind_d = (TextView) findViewById(R.id.tv_wind_d);
        tv_wind_s = (TextView) findViewById(R.id.tv_wind_s);
        tv_time = (TextView) findViewById(R.id.tv_time);

        weatherModel = new WeatherModelImpl(this);
        ld = new LoadingDialog(this);
        ld.setLoadingText("正在獲取天氣...");
        ld.show();
        weatherModel.getWeather(this);
    }

    private void onShowWeather(WeatherInfo weatherInfo){
        tv_name.setText(weatherInfo.city);
        tv_temperature.setText(weatherInfo.temp+"");
        tv_wind_d.setText(weatherInfo.WD);
        tv_wind_s.setText(weatherInfo.WS);
        tv_time.setText(weatherInfo.time);
    }

    @Override
    public void onLoadSuccess(WeatherInfo info) {
        ld.dismiss();
        onShowWeather(info);
    }

    @Override
    public void onError(NetError error) {
        ld.dismiss();
        T.getInstance().showShort(error.errorCode +" "+ error.errorMessage);
    }
}複製程式碼

  程式碼github.com/zhaozepeng/… =),所以有這麼一句話

Most of the modern Android applications just use View-Model architecture,everything is connected with Activity.

所以這時候可以繼續把Activity拆分,Activity只控制view和接受使用者的輸入,另外新建一個Controller類,這個類不能繼承任何Android自帶類,用來將邏輯拆分出來,避免Activity的難以維護,具體可以看看這個例子mrbool.com/android-mvc…

MVP

  Model, View and Presenter模式,MVP模式和MVC模式類似,是由MVC演變而來,MVP將Controller變成Presenter,並且改變了通訊方向,這個模式將應用分為三個主要層級:Model, View and Presenter。
  

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

可以看到Presenter與Model,Presenter與View的通訊都是雙向的,View不與Model發生關係,都是通過Presenter來傳遞,所以Presenter的業務量會顯的非常大,三層之間的互動關係為:

  1. View接受使用者的互動請求
  2. View將請求轉交給Presenter
  3. Presenter操作Model進行資料庫更新
  4. 資料更新之後,Model通知Presenter資料發生變化
  5. Presenter更新View層的顯示
Model和View層之間是沒有互動的,這是和MVC不同的一點:
  • Model層
  • 該層通常是用來處理業務邏輯和實體模型。
  • View層
  • 通常是一個Activity或者Fragment或者View,這取決於應用的結構,它會持有一個Presenter層的引用,所以View唯一做的事情就是在有使用者互動等操作時呼叫Presenter層的方法。
  • Presenter層
  • 該層用來作為一箇中間層的角色,它接受Model層的資料,並且處理之後傳遞給View層,還需要處理View層的使用者互動等操作。
View和Presenter的一對一關係意味著一個View只能對映到一個Presenter上,並且View只有Presenter的引用,沒有Model的引用,所以Presenter和View是一個雙向的互動。Presenter不管View層的UI佈局,View的UI佈局變更,Presenter層不需要做任何修改。

例子

  MVP 的寫法就有很多了,不同人對於 MVP 的寫法各有不同,在遵循物件導向的設計原則基礎上都是可以的,這裡我就以 google 大大的官方 demo 和一個外國大神的 MVP demo 為例來分析一下,用哪種形式不要糾結,最重要的是自己用起來順手。

google 官方寫法

  先貼出來原始碼地址:github.com/googlesampl…
  

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

  在該 demo 中,有一個 BaseView 和 BasePresenter ,並且使用泛型來定義,作用是定義該模組不同 View 和 Presenter 的基礎行為:
BaseView.class

public interface BaseView<T> {

    void setPresenter(T presenter);

}複製程式碼

BasePresenter.class

public interface BasePresenter {

    void start();

}複製程式碼

之後的每一個頁面的 View 和 Presenter 都要繼承自 BaseView 和 BaseAdapter ,在 demo 中,View 和 Presenter 的介面類都定義在一個 TasksContract 類中:
TasksContract.class

public interface TasksContract {

    interface View extends BaseView<Presenter> {

        void setLoadingIndicator(boolean active);

        void showTasks(List<Task> tasks);

        void showAddTask();

        void showTaskDetailsUi(String taskId);

        void showTaskMarkedComplete();

        void showTaskMarkedActive();

        void showCompletedTasksCleared();

        void showLoadingTasksError();

        void showNoTasks();

        void showActiveFilterLabel();

        void showCompletedFilterLabel();

        void showAllFilterLabel();

        void showNoActiveTasks();

        void showNoCompletedTasks();

        void showSuccessfullySavedMessage();

        boolean isActive();

        void showFilteringPopUpMenu();
    }

    interface Presenter extends BasePresenter {

        void result(int requestCode, int resultCode);

        void loadTasks(boolean forceUpdate);

        void addNewTask();

        void openTaskDetails(@NonNull Task requestedTask);

        void completeTask(@NonNull Task completedTask);

        void activateTask(@NonNull Task activeTask);

        void clearCompletedTasks();

        void setFiltering(TasksFilterType requestType);

        TasksFilterType getFiltering();
    }
}複製程式碼

之後就是實現這兩個基礎介面了,demo 中使用的是 Fragment 作為 View 的角色,把邏輯從 Activity 中抽離出來,這個看自己的程式設計習慣吧,只使用 Activity 還是使用 Fragment。先看看 Activity 的程式碼:
AppCompatActivity.class

public class TasksActivity extends AppCompatActivity {

    private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";

    private DrawerLayout mDrawerLayout;

    private TasksPresenter mTasksPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.tasks_act);

        // Set up the toolbar.
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar ab = getSupportActionBar();
        ab.setHomeAsUpIndicator(R.drawable.ic_menu);
        ab.setDisplayHomeAsUpEnabled(true);

        // Set up the navigation drawer.
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);
        NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
        if (navigationView != null) {
            setupDrawerContent(navigationView);
        }

        TasksFragment tasksFragment =
                (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
        if (tasksFragment == null) {
            // Create the fragment
            tasksFragment = TasksFragment.newInstance();
            ActivityUtils.addFragmentToActivity(
                    getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
        }

        // Create the presenter
        mTasksPresenter = new TasksPresenter(
                Injection.provideTasksRepository(getApplicationContext()), tasksFragment);

        // Load previously saved state, if available.
        if (savedInstanceState != null) {
            TasksFilterType currentFiltering =
                    (TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
            mTasksPresenter.setFiltering(currentFiltering);
        }
    }
...
}複製程式碼

可以看到程式碼中,通過 Fragment 的靜態方法獲取到一個 Fragment ,並且新增到 activity 中,那麼現在有一個疑問了, Presenter 是如何設定到 Fragment 中的呢?我們接下來看看 Presenter 類:
TasksPresenter.class

public class TasksPresenter implements TasksContract.Presenter {
    ...
    public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
        mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");

        mTasksView.setPresenter(this);
    }

    @Override
    public void start() {
        loadTasks(false);
    }

    ...

}複製程式碼

可以看到是在 Presenter 的建構函式中 set 的,這也就沒問題了,官方的 MVP 框架就介紹完了,大家去看一下原始碼就很清楚了。

其他可參考寫法

  另外一個外國人寫的例子,分析一下:
  

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

LoginActivity繼承自LoginView;LoginPresenterImpl繼承自LoginPresenter;LoginInteractorImpl繼承自LoginInteractor,所以MVP模式三個層次之間是通過介面來進行互動的,看看原始碼:
LoginInteractorImpl.class類

public class LoginInteractorImpl implements LoginInteractor {

    @Override
    public void login(final String username, final String password, final OnLoginFinishedListener listener) {
        // Mock login. I'm creating a handler to delay the answer a couple of seconds
        new Handler().postDelayed(new Runnable() {
            @Override public void run() {
                boolean error = false;
                if (TextUtils.isEmpty(username)){
                    listener.onUsernameError();
                    error = true;
                }
                if (TextUtils.isEmpty(password)){
                    listener.onPasswordError();
                    error = true;
                }
                if (!error){
                    listener.onSuccess();
                }
            }
        }, 2000);
    }
}複製程式碼

LoginActivity.class類

public class LoginActivity extends Activity implements LoginView, View.OnClickListener {

    private ProgressBar progressBar;
    private EditText username;
    private EditText password;
    private LoginPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        progressBar = (ProgressBar) findViewById(R.id.progress);
        username = (EditText) findViewById(R.id.username);
        password = (EditText) findViewById(R.id.password);
        findViewById(R.id.button).setOnClickListener(this);

        presenter = new LoginPresenterImpl(this);
    }

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

    @Override public void showProgress() {
        progressBar.setVisibility(View.VISIBLE);
    }

    @Override public void hideProgress() {
        progressBar.setVisibility(View.GONE);
    }

    @Override public void setUsernameError() {
        username.setError(getString(R.string.username_error));
    }

    @Override public void setPasswordError() {
        password.setError(getString(R.string.password_error));
    }

    @Override public void navigateToHome() {
        startActivity(new Intent(this, MainActivity.class));
        finish();
    }

    @Override public void onClick(View v) {
        presenter.validateCredentials(username.getText().toString(), password.getText().toString());
    }
}複製程式碼

LoginPresenterImpl.class類

public class LoginPresenterImpl implements LoginPresenter, OnLoginFinishedListener {

    private LoginView loginView;
    private LoginInteractor loginInteractor;

    public LoginPresenterImpl(LoginView loginView) {
        this.loginView = loginView;
        this.loginInteractor = new LoginInteractorImpl();
    }

    @Override public void validateCredentials(String username, String password) {
        if (loginView != null) {
            loginView.showProgress();
        }

        loginInteractor.login(username, password, this);
    }

    @Override public void onDestroy() {
        loginView = null;
    }

    @Override public void onUsernameError() {
        if (loginView != null) {
            loginView.setUsernameError();
            loginView.hideProgress();
        }
    }

    @Override public void onPasswordError() {
        if (loginView != null) {
            loginView.setPasswordError();
            loginView.hideProgress();
        }
    }

    @Override public void onSuccess() {
        if (loginView != null) {
            loginView.navigateToHome();
        }
    }
}複製程式碼

  程式碼層次很清楚,View層接受使用者的點選操作,回撥Presenter層的相關介面,Presenter層再呼叫到Model層去執行登入操作,同時修改View層的Progress顯示情況,Model層執行完登入操作之後,回撥到Presenter層的對應介面,Presenter再去對View層的佈局進行相應的修改。原始碼github.com/zhaozepeng/…

MVVM

  Model,View and ViewModel模式,MVVM 模式將 Presenter 改名為 ViewModel,基本上與 MVP 模式完全一致,ViewModel可以理解成是View的資料模型和Presenter的合體,MVVM採用雙向繫結(data-binding):View的變動,自動反映在 ViewModel,反之亦然,這種模式實際上是框架替應用開發者做了一些工作,開發者只需要較少的程式碼就能實現比較複雜的互動。
  

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

  • Model
  • 類似MVP
  • View
  • 類似MVP
  • ViewModel
  • 注意這裡的“Model”指的是View的Model,跟上面那個Model不是一回事。所謂View的Model就是包含View的一些資料屬性和操作的東西。

例子

  這種模式的關鍵技術就是資料繫結(data binding)。
  在android中已經有了相應的外掛框架,比如 RoboBinding這個框架,但是好像侵入性太強,普及程度不高,所以可以看看全面介紹Android的MVVM框架 - 資料繫結這篇部落格,它使用了databinding依賴庫進行處理,講的很好。
  也可以看看github.com/fabioCollin…

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

裡面的介紹也非常清楚。

MVC VS MVP VS MVVM

  

android MVC && MVP && MVVM分析和對比
這裡寫圖片描述

  MVP模式是從MVC模式演變來的,它們的基本思想有相通的地方:Controller/Presenter負責邏輯的處理,Model提供資料,View負責顯示,所以他們之間並沒有特別大的不同,都是用來將View和Model之間鬆耦合。作為一種新的模式,MVP與MVC有著一個重大的區別:在MVP中View並不直接使用Model,它們之間的通訊是通過Presenter (MVC中的Controller)來進行的,所有的互動都發生在Presenter內部,而在MVC中是允許Model和View進行互動的。還有重要的一點就是Presenter與View之間的互動是通過介面的。MVVM是通過MVP演變而來的,主要用了data binding來實現雙向互動,這就使得檢視和控制層之間的耦合程度進一步降低,關注點分離更為徹底,同時減輕了Activity的壓力。
關鍵點總結:

  • MVC

  1. Controller是基於行為,並且能夠在view之間共享
  2. Controller負責接收使用者互動等操作,並且決定需要顯示的檢視。
  • MVP
    1. View和Model更加的解耦了,Presenter負責繫結Model到View。
    2. 複雜的View可以對應多個Persenter。
    3. Presenter保留有View層的事件邏輯,所有的點選之類的事件都直接委託給Presenter。
    4. Presenter通過介面直接和View層解耦,所以更加方便的進行View的單元測試。
    5. Presenter和其他兩層都是雙向呼叫的。
    6. MVP有兩種實現方式:"Passive View",View基本包含0邏輯, Presenter作為View和Model的中間人,View和Model相互隔離,View和Model沒有直接的資料繫結,取而代之的是View提供相關的setter方法供Persenter去呼叫,這麼做的好處是View和Model乾淨的分離開了,所以更好的進行相關測試,缺點是需要提供很多的setter方法;"Supervising Controller",Persenter處理使用者互動等的操作,View和Model直接通過資料繫結連線,這種模式下,Persenter的任務就是將實體直接通過Model層傳遞給View層,這種方法的好處就是程式碼量少了,但是缺點就是測試難度增大,並且View的封裝性變低。
    7. MVVM
      1. 使用者直接互動的是View。
      2. View和ViewModel是多對一的關係。
      3. View有ViewModel的引用,但是ViewModel沒有任何關於View的資訊。
      4. 支援View和ViewModel的雙向資料繫結。

      引用

      antonioleiva.com/mvp-android…
      medium.com/android-new…
      kb.cnblogs.com/page/120678…
      www.ruanyifeng.com/blog/2015/0…
      www.cnblogs.com/devinzhang/…
      mrbool.com/android-mvc…
      www.bogotobogo.com/DesignPatte…
      www.cnblogs.com/cuihongyu35…
      droidumm.blogspot.com/2011/11/con…
      blog.csdn.net/lmj62356579…
      stackoverflow.com/questions/2…
      blog.csdn.net/feelang/art…
      blog.csdn.net/napolunyish…
      www.zhihu.com/question/30…

      相關文章