zoukankan      html  css  js  c++  java
  • android MVC && MVP && MVVM分析和对比

     

    目录(?)[+]

     

      面试的时候被问到这个问题,用过,也了解过,但是还是不够深入,总结一下。 
      MVC,MVP和MVVM是软件比较常用的三种软件架构,这三种架构的目的都是分离关注,避免将过多的逻辑全部堆积在一个类中,以Android为例,在activity中既有UI的相关处理逻辑,又有数据获取逻辑,从而导致activity逻辑复杂不单一难以维护。为了一个应用可以更好的维护和扩展,我们需要很好的区分相关层级,要不然以后将数据获取方式从数据库变为网络获取时,我们需要去修改整个activity。架构使得view和数据相互独立,我们把应用分成三个不同层级,这样我们就能够单独测试相关层级,使用架构能够把大多数逻辑从activity中移除,方便进行单元测试。

    MVC

      Model View Controller模式,MVC将应用分成三个主要层级:Model,View和Controller,它强制将逻辑进行分离,数据结构和Controller逻辑与UI是解耦的,所以测试相关模块变的更简单。 
      这里写图片描述 
      其实android app的界面开发部分已经是遵从MVC模式的,

    • 视图层(View):一般采用XML文件进行界面的描述,使用的时候可以非常方便的引入,当然,也可以使用JavaScript+HTML等的方式作为View层,他的职责就是负责显示从Controller上获取到的数据(但是xml布局作为View来说功能很无力,所以通常Activity也会承担一部分View的工作)。
    • 控制层(Controller):Android的控制层的重任通常落在了众多的Activity的肩上,他们从模型层获取数据,将获取到的数据绑定到view上,并且还需要监听用户的输入等操作。
    • 模型层(Model):对数据库的操作、对网络等的操作都应该在Model里面处理,当然对业务计算,变更等操作也是必须放在的该层的。
    MVC模式具体表现在android上的效果如下图所示: 
    这里写图片描述 
      也可以看看这个视频,介绍的不错: 
      这里写图片描述 
    还有另外的,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);
        }
    }

      代码https://github.com/zhaozepeng/MV-X。这个例子逻辑很简单,Controller层的Activity持有Model层WeatherModel的对象,然后通过该对象去获取数据,获取到数据之后,通过View层去显示。但是上面有提到过xml布局文件作为View层,其实能做的事情特别少,实际上关于该布局文件中的数据绑定的操作,事件处理的代码都在Activity中,造成了Activity既像View又像Controller(在逻辑简单的情况下,视图层和控制层写在一起貌似可以减少几个类= =),所以有这么一句话

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

    所以这时候可以继续把Activity拆分,Activity只控制view和接受用户的输入,另外新建一个Controller类,这个类不能继承任何Android自带类,用来将逻辑拆分出来,避免Activity的难以维护,具体可以看看这个例子http://mrbool.com/android-mvc-creating-a-model-view-controller-framework-for-android/32335

    MVP

      Model, View and Presenter模式,MVP模式和MVC模式类似,是由MVC演变而来,MVP将Controller变成Presenter,并且改变了通信方向,这个模式将应用分为三个主要层级:Model, View and Presenter。 
      这里写图片描述 
    可以看到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 官方写法

      先贴出来源码地址:https://github.com/googlesamples/android-architecture/tree/todo-mvp/todoapp,分析一下: 
      这里写图片描述 
      在该 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 框架就介绍完了,大家去看一下源码就很清楚了。

    其他可参考写法

      另外一个外国人写的例子,分析一下: 
      这里写图片描述 
    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层的布局进行相应的修改。源码https://github.com/zhaozepeng/MV-X,这里也给一下源链接:https://github.com/antoniolg/androidmvp

    MVVM

      Model,View and ViewModel模式,MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致,ViewModel可以理解成是View的数据模型和Presenter的合体,MVVM采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然,这种模式实际上是框架替应用开发者做了一些工作,开发者只需要较少的代码就能实现比较复杂的交互。 
      这里写图片描述

    • Model
    • 类似MVP
    • View
    • 类似MVP
    • ViewModel
    • 注意这里的“Model”指的是View的Model,跟上面那个Model不是一回事。所谓View的Model就是包含View的一些数据属性和操作的东西。

    例子

      这种模式的关键技术就是数据绑定(data binding)。 
      在android中已经有了相应的插件框架,比如 RoboBinding这个框架,但是好像侵入性太强,普及程度不高,所以可以看看全面介绍Android的MVVM框架 - 数据绑定这篇博客,它使用了databinding依赖库进行处理,讲的很好。 
      也可以看看https://github.com/fabioCollini/mv2m,外国人写的一个库,进行了很多其他的封装,可以参考一下: 
    这里写图片描述 
    里面的介绍也非常清楚。

    MVC VS MVP VS 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的封装性变低。
    • MVVM
      1. 用户直接交互的是View。
      2. View和ViewModel是多对一的关系。
      3. View有ViewModel的引用,但是ViewModel没有任何关于View的信息。
      4. 支持View和ViewModel的双向数据绑定。

    引用

    http://antonioleiva.com/mvp-android/ 
    https://medium.com/android-news/android-architecture-2f12e1c7d4db#.aaytu7yff 
    http://kb.cnblogs.com/page/120678/ 
    http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html 
    http://www.cnblogs.com/devinzhang/archive/2012/01/26/2329869.html 
    http://mrbool.com/android-mvc-creating-a-model-view-controller-framework-for-android/32335 
    http://www.bogotobogo.com/DesignPatterns/mvc_model_view_controller_pattern.php 
    http://www.cnblogs.com/cuihongyu3503319/archive/2009/01/09/1372820.html 
    http://droidumm.blogspot.com/2011/11/concept-model-view-present-mvp-pattern.html 
    http://blog.csdn.net/lmj623565791/article/details/46596109 
    http://stackoverflow.com/questions/2056/what-are-mvp-and-mvc-and-what-is-the-difference 
    http://blog.csdn.net/feelang/article/details/46348079 
    http://blog.csdn.net/napolunyishi/article/details/22722345 
    http://www.zhihu.com/question/30976423

     相关:

    Android App的设计架构:MVC,MVP,MVVM与架构经验谈

    Google 官方MVP Demo 

    Android MVP 详解(上)

    Android MVP 详解(下)

  • 相关阅读:
    MVC页面打印成pdf插件Rotativa
    lambda表达式嵌套
    MVC+EF五种条件查询获取数据列表方式
    自己写的通用分页存储过程
    Llinux命令大全之软件管理命令
    .net开发 chrome核心浏览器组件
    常用的SQL日期操作
    Linux各版本介绍说明
    大数据环境搭建
    markdown语法链接新页面打开
  • 原文地址:https://www.cnblogs.com/wytiger/p/5996876.html
Copyright © 2011-2022 走看看