欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android中MVC与MVP学习笔记

程序员文章站 2024-03-15 18:19:18
...

第1部分:Android中的MVC

1、View

一般采用XML文件进行界面描述,这些XML可以理解为App中的View。

2、Controller

控制层的重任通过落在了Activity的户上。但是一般不要把所有的控制代码都放到Activity中,一方面会造成Activity极为臃肿,不方便维护;另一方面,如果Activity中的代码量过大,响应时间太长,会造成ANR。

3、Model

对数据(数据库、网络数据、文件等)的操作都应该放到Model层。Model是与View无关,而与业务有关的。

4、MVC示例

View层

Android中MVC与MVP学习笔记

Controller层

public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {

    private WeatherModel weatherModel;
    private EditText cityNOInput;
    private TextView city;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weatherModel = new WeatherModelImpl();
        initView();
    }

    //初始化View
    private void initView() {
        cityNOInput = findView(R.id.et_city_no);
        city = findView(R.id.tv_city);
        ...
        findView(R.id.btn_go).setOnClickListener(this);
    }

    //显示结果
    public void displayResult(Weather weather) {
        WeatherInfo weatherInfo = weather.getWeatherinfo();
        city.setText(weatherInfo.getCity());
        ...
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_go:
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
                break;
        }
    }

    @Override
    public void onSuccess(Weather weather) {
        displayResult(weather);
    }

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

    private T findView(int id) {
        return (T) findViewById(id);
    }
}

Model层

public interface WeatherModel {
    void getWeather(String cityNumber, OnWeatherListener listener);
}

................

public class WeatherModelImpl implements WeatherModel {
    /*这部分代码范例有问题,网络访问不应该在Model中,应该把网络访问换成从数据库读取*/
    @Override
    public void getWeather(String cityNumber, final OnWeatherListener listener) {

        /*数据层操作*/
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
                Weather.class, new Response.Listener<weather>() {
                    @Override
                    public void onResponse(Weather weather) {
                        if (weather != null) {
                            listener.onSuccess(weather);
                        } else {
                            listener.onError();
                        }
                    }
                }, new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        listener.onError();
                    }
                });
    }
}

说明

以上代码看出,这里设计了一个WeatherModel模型接口,然后实现了接口WeatherModelImpl类。controller控制器activity调用WeatherModelImpl类中的方法发起网络请求,然后通过实现OnWeatherListener接口来获得网络请求的结果通知View视图层更新UI 。至此,Activity就将View视图显示和Model模型数据处理隔离开了。activity担当contronller完成了model和view之间的协调作用。

至于这里为什么不直接设计成类里面的一个getWeather()方法直接请求网络数据?你考虑下这种情况:现在代码中的网络请求是使用Volley框架来实现的,如果哪天老板非要你使用Afinal框架实现网络请求,你怎么解决问题?难道是修改 getWeather()方法的实现? no no no,这样修改不仅破坏了以前的代码,而且还不利于维护, 考虑到以后代码的扩展和维护性,我们选择设计接口的方式来解决着一个问题,我们实现另外一个WeatherModelWithAfinalImpl类,继承自WeatherModel,重写里面的方法,这样不仅保留了以前的WeatherModelImpl类请求网络方式,还增加了WeatherModelWithAfinalImpl类的请求方式。Activity调用代码无需要任何修改。

第2部分:MVP设计架构

在App开发过程中,经常出现的问题是某一部分代码量过大,虽然做了模块划分和接口隔离,但也很难完全避免。从实践中看到,这更多的出现在UI部分,也就是Activity里。想象一下,一个2000+行以上基本不带注释的Activity,我的第一反应就是想吐。Activity内容过多的原因其实很好解释,因为Activity本身需要担负与用户之间的操作交互,界面的展示,不是单纯的Controller或View。而且现在大部分的Activity还对整个App起到类似IOS中的【ViewController】的作用,这又带入了大量的逻辑代码,造成Activity的臃肿。为了解决这个问题,让我们引入MVP框架。

MVC的缺点

在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户 界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。

什么是MVP

Android中MVC与MVP学习笔记

MVP框架由3部分组成:View负责显示,Presenter负责逻辑处理,Model提供数据。在MVP模式里通常包含3个要素(加上View interface是4个):

  • View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)
  • Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)
  • Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。
  • View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试

View interface的必要性

回想一下你在开发Android应用时是如何对代码逻辑进行单元测试的?是否每次都要将应用部署到Android模拟器或真机上,然后通过模拟用 户操作进行测试?然而由于Android平台的特性,每次部署都耗费了大量的时间,这直接导致开发效率的降低。而在MVP模式中,处理复杂逻辑的Presenter是通过interface与View(Activity)进行交互的,这说明我们可以通过自定义类实现这个interface来模拟Activity的行为对Presenter进行单元测试,省去了大量的部署及测试的时间。

第3部分:MVC → MVP

当我们将Activity复杂的逻辑处理移至另外的一个类(Presenter)中时,Activity其实就是MVP模式中的View,它负责UI元素的初始化,建立UI元素与Presenter的关联(Listener之类),同时自己也会处理一些简单的逻辑(复杂的逻辑交由 Presenter处理)。

MVP的Presenter是框架的控制者,承担了大量的逻辑操作,而MVC的Controller更多时候承担一种转发的作用。因此在App中引入MVP的原因,是为了将此前在Activty中包含的大量逻辑操作放到控制层中,避免Activity的臃肿。

两者的比较

  • (最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
  • 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
  • Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。

Android中MVC与MVP学习笔记

MVP的优点

  1. 模型与视图完全分离,我们可以修改视图而不影响模型;
  2. 可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部
  3. 我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁;
  4. 如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)。

总结:

  • 具体到Android App中,一般可以将App根据程序的结构进行纵向划分,根据MVP可以将App分别为模型层(M),UI层(V)和逻辑层(P)。
  • UI层一般包括Activity,Fragment,Adapter等直接和UI相关的类,UI层的Activity在启动之后实例化相应的Presenter,App的控制权后移,由UI转移到Presenter,两者之间的通信通过BroadCast、Handler或者接口完成,只传递事件和结果。
  • 举个简单的例子,UI层通知逻辑层(Presenter)用户点击了一个Button,逻辑层(Presenter)自己决定应该用什么行为进行响应,该找哪个模型(Model)去做这件事,最后逻辑层(Presenter)将完成的结果更新到UI层。

第4部分:MVP的变种:Passive View

MVP的变种有很多,其中使用最广泛的是Passive View模式,即被动视图。在这种模式下,View和Model之间不能直接交互,View通过Presenter与Model打交道。Presenter接受View的UI请求,完成简单的UI处理逻辑,并调用Model进行业务处理,并调用View将相应的结果反映出来。View直接依赖Presenter,但是Presenter间接依赖View,它直接依赖的是View实现的接口。

Android中MVC与MVP学习笔记

相对于View的被动,那Presenter就是主动的一方。对于Presenter的主动,有如下的理解:

  • Presenter是整个MVP体系的控制中心,而不是单纯的处理View请求的人;
  • View仅仅是用户交互请求的汇报者,对于响应用户交互相关的逻辑和流程,View不参与决策,真正的决策者是Presenter;
  • View向Presenter发送用户交互请求应该采用这样的口吻:“我现在将用户交互请求发送给你,你看着办,需要我的时候我会协助你”,不应该是这样:“我现在处理用户交互请求了,我知道该怎么办,但是我需要你的支持,因为实现业务逻辑的Model只信任你”;
  • 对于绑定到View上的数据,不应该是View从Presenter上“拉”回来的,应该是Presenter主动“推”给View的;
  • View尽可能不维护数据状态,因为其本身仅仅实现单纯的、独立的UI操作;Presenter才是整个体系的协调者,它根据处理用于交互的逻辑给View和Model安排工作。

第5部分:MVP架构存在的问题与解决办法

  • 问题1:转移逻辑操作之后可能部分较为复杂的Activity内代码量还是不少。
  • 解决思路:在分层的基础上再加入模板方法(Template Method)。
  • 具体操作:
    • 在Activity内部分层。
    • 其中最顶层为BaseActivity,不做具体显示,而是提供一些基础样式,Dialog,ActionBar在内的内容。
    • 展现给用户的Activity继承BaseActivity,重写BaseActivity预留的方法。
    • 如有必要再进行二次继承,App中Activity之间的继承次数最多不超过3次。

  • 问题2:模型层(Model)中的整体代码量是最大的。
  • 解决思路:内容分层。
  • 具体操作:做好模块的划分,进行接口隔离,在内部进行分层。

  • 问题3:Presenter中的逻辑过重
  • 解决思路:在UI层和Presenter之间设置中介者Mediator。
  • 具体操作:
    • 将例如数据校验、组装在内的轻量级逻辑操作放在Mediator中;
    • 在Presenter和Model之间使用代理Proxy;
    • 通过上述两者分担一部分Presenter的逻辑操作,但整体框架的控制权还是在Presenter手中。Mediator和Proxy不是必须的,只在Presenter负担过大时才建议使用。

最终架构图:

Android中MVC与MVP学习笔记

第6部分:MVP代码示例

包结构

Android中MVC与MVP学习笔记

代码示例

Bean

public class UserBean {
     private String mFirstName;
     private String mLastName;
     public UserBean(String firstName, String lastName) {
            this. mFirstName = firstName;
            this. mLastName = lastName;
     }
     public String getFirstName() {
            return mFirstName;
     }
     public String getLastName() {
            return mLastName;
     }
}

Model

public interface IUserModel {
     void setID(int id);

     void setFirstName(String firstName);

     void setLastName(String lastName);

     int getID();

     UserBean load(int id);// 通过id读取user信息,返回一个UserBean
}

Presenter

public class UserPresenter {
     private IUserView mUserView;
     private IUserModel mUserModel;

     public UserPresenter(IUserView view) {
            mUserView = view;
            mUserModel = new UserModel();
     }

     public void saveUser( int id, String firstName, String lastName) {
            mUserModel.setID(id);
            mUserModel.setFirstName(firstName);
            mUserModel.setLastName(lastName);
     }

     public void loadUser( int id) {
           UserBean user = mUserModel.load(id);
            mUserView.setFirstName(user.getFirstName()); // 通过调用IUserView的方法来更新显示
            mUserView.setLastName(user.getLastName());
     }
}

View

public interface IUserView {
     int getID();

     String getFristName();

     String getLastName();

     void setFirstName(String firstName);

     void setLastName(String lastName);
}
public class MainActivity extends Activity implements OnClickListener,IUserView {

     UserPresenter presenter;
     EditText id,first,last;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
           setContentView(R.layout. activity_main);

           findViewById(R.id. save).setOnClickListener( this);
           findViewById(R.id. load).setOnClickListener( this);
            id = (EditText) findViewById(R.id. id);
            first = (EditText) findViewById(R.id. first);
            last = (EditText) findViewById(R.id. last);

            presenter = new UserPresenter( this);
     }

     @Override
     public void onClick(View v) {
            switch (v.getId()) {
            case R.id. save:
                 presenter.saveUser(getID(), getFristName(), getLastName());
                 break;
            case R.id. load:
                 presenter.loadUser(getID());
                 break;
            default:
                 break;
           }
     }

     @Override
     public int getID() {
            return new Integer( id.getText().toString());
     }

     @Override
     public String getFristName() {
            return first.getText().toString();
     }

     @Override
     public String getLastName() {
            return last.getText().toString();
     }

     @Override
     public void setFirstName(String firstName) {
            first.setText(firstName);
     }

     @Override
     public void setLastName(String lastName) {
            last.setText(lastName);
     }

}

第7部分:总结

Activity及从MVC中的Controller中解放出来了,这会Activity主要做显示View的作用和用户交互。每个Activity可以根据自己显示View的不同实现View视图接口IUserView。

通过对比同一实例的MVC与MVP的代码,可以证实MVP模式的一些优点:

  • 在MVP中,Activity的代码不臃肿;
  • 在MVP中,Model(IUserModel的实现类)的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;
  • 在MVP中,IUserView这个接口可以实现方便地对Presenter的测试;
  • 在MVP中,UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。
相关标签: Android MVC MVP