在一个项目开始之前,开发者就应该根据项目的大小和内容决定用什么设计模式和框架,选好一个合适的设计模式,可以使项目开发分工更明确,效率更高效,出错率更低,维护更容易。
  在我看来,设计模式就是一种把一个项目的代码分成若干个小块,让每个小块的耦合性尽量地降低,让每个模块的代码可重用性增强,让项目的维护更简单的方法。下面我们来学习MVC模式和它的改进模式MVP模式,关于这两个模式我在网上看了很多资料,似乎虽然很多人都对这两个模式在大体上看法是相同的,但是一些细节上区别还是很大的,我估计和他们的项目经验有关,下面是我自己总结的一点看法。

一、MVC模式

  MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,这种是一种软件设计模式,不单只是应用在Android开发里面,所有软件设计都有这种模式。

  其中M层里面有数据库存取操作,网络操作,复杂的算法,耗时的任务等都在model层;V层处理界面的显示结果,XML布局可以视为V层;C层起到桥梁的作用,一般Activity是控制器,Activity读取V视图层的数据,控制用户输入,并向Model发送数据请求。

  然而,在Android开发里面,并不能把这3层结构清晰地分开,Activity在MVC模式中兼顾着View层和C层的作用。

  

MVC优点:

  • 使项目模块代码之间的耦合性降低。
  • 使项目有很好的可扩展性和和维护性

MVC缺点:

  • 项目越大,UI越复杂,而且Model层和View层还有交互,Activity会越来越复杂,检查和维护起来会非常的麻烦。

二、MVP模式

  随着UI创建技术的功能日益增强,UI层也履行着越来越多的职责。为了更好地细分视图(View)与模型(Model)的功能,让View专注于处理数据的可视化以及与用户的交互,同时让Model只关系数据的处理,基于MVC概念的MVP(Model-View-Presenter)模式应运而生。

  MVP模式是从经典的MVC模式演变过来的,它就是把MVC里面Controller从Activity取出去,让Activity的代码专注于UI界面的控制,然后切断了View和Model的联系,取出来的一部分给了一个新的名称Presenter,用来连接View和Model。

MVP优点

  • 完全切断了Model层和View层的联系,使项目模块之间的耦合性更低了,写Model模块的人和做UI模块的人可以完全不用理对方想什么(夸张点说)。
  • 方便进行单元测试,可以方便地在Presenter写测试代码,不用因为正式代码和测试代码混在一起而搞得混乱。
  • 可以尽量避免 Activity 的内存泄露,当APP的内存不够用的时候,系统会自动回收处于后台的Activity的资源,在传统的MVC模式中,一大堆异步任务和对UI的操作都放在Activity里面,异步任务的线程对象之类的可能在Activity结束之后还没有结束,保持着对Activity实例的引用,所以系统就无法回收这个Activity实例了,结果就是Activity的内存泄露,MVP模式中把需要的异步任务操作都放到modle层,所以只要在当前的Activity的onDestroy里解除对Presenter层的引用,就可以很方便地避免内存泄漏。

MVP缺点

  • 应用它需要额外的学习知识与代码架构。从我下面的例子可以看出,一个很简单的词典功能,为了实现MVP结构,不但要把Context调来调去,还要各种回调,逻辑看起来比较混乱。

MVP模式的使用


通过MVP模式的UML图可以看出,使用MVP,至少需要经历以下步骤:

1. 创建IPresenter接口,把所有业务逻辑的接口都放在这里,并创建它的实现PresenterCompl

由于接口可以有多种实现,方式多样且灵活,所以在这里可以方便地定位到相应的业务功能和单元测试
2. 创建IView接口,把所有视图逻辑的接口都放在这里,其实现类是当前的Activity/Fragment
由UML图可以看出,Activity里包含了一个IPresenter,而PresenterCompl里又包含了一个IView并且依赖了Model。Activity里只保留对IPresenter的调用,其它工作全部留到PresenterCompl中实现
3. Model并不是必须有的,但是一定会有View和Presenter

MVP模式的例子:

我采用自己所理解的MVP模式做了一个简单的英语词典app,借此来学习MVP模式具体的实现:

首先是View层:
View层里面有View接口和处理UI的MainActivity

package scut.cidian.View;

/**
 * Created by yany on 2016/4/6.
 */
public interface fanyiView {
    void init();//初始化
    void SetInfo(String str);//输出翻译信息
    void SetError();//输出出错信息
}

MainActivity.java

package scut.cidian.View;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import scut.cidian.Presenter.CidianPresenter;
import scut.cidian.R;

public class MainActivity extends AppCompatActivity implements fanyiView {
    private CidianPresenter cidianPresenter;
    private EditText et;
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        findViewById(R.id.btnfanyi).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                cidianPresenter.ModelGetData(et.getText().toString(), MainActivity.this);
            }
        });
    }
    public void init(){
        cidianPresenter = new CidianPresenter(this);
        et = (EditText) findViewById(R.id.editText);
        tv = (TextView) findViewById(R.id.tv);
    }//初始化

    @Override
    public void SetError() {
        tv.setText("查询不成功,请检查网络");
    }//输出出错信息

    public void SetInfo(String str){
        tv.setText(str);
    }//输出翻译信息
}

然后是Model层:
我这里建立了一个简易的JavaBean,由于只是用来学习我就不把里面的属性设为private了,免得写set和get方法。
fanyi.java:

package scut.cidian.Model;
import java.util.List;

public class fanyi {
    public String[] translation;
    public basic basic;
    public  static class basic{
        public String phonetic;
        public String[] explains;
    }
    public String query;
    public int errorCode;
    public List<wb> web;
    public static class wb{
            public String[] value;
            public String key;
        }
}

下面这个是Model层用来读取网络数据的fanyimodel.java和它的接口Ifanyimodel,使用的Google开源的Volley框架,其实我这里并没有用到这个接口,只是为了以后方便扩展才加上去。
Ifanyimodel.java:

package scut.cidian.Model;

import android.content.Context;

import scut.cidian.Presenter.onfanyiListener;

/**
 * Created by yany on 2016/4/5.
 */
public interface Ifanyimodel {
    void HandleData(String input,Context context,onfanyiListener listener);
    String fanyiToString(fanyi fy);
}

fanyimodel.java:

package scut.cidian.Model;

import android.content.Context;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
import com.google.gson.Gson;
import scut.cidian.Presenter.onfanyiListener;


public class fanyimodel {
    private fanyi fy = new fanyi();
    public void HandleData(String input,Context context,final onfanyiListener listener){
        //使用Volley框架来实现异步从网络的有道API获取翻译数据
        RequestQueue mQueue = Volley.newRequestQueue(context);
        StringRequest stringRequest = new StringRequest("http://fanyi.youdao.com/openapi.do?keyfrom=Yanzhikai&key=2032414398&type=data&doctype=json&version=1.1&q="+input, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                //用Gson方式解析获得的json字符串
                Gson gson = new Gson();
                fy = gson.fromJson(s.trim(),fy.getClass());
                //回调监听器的函数
                listener.onSuccess(fanyiToString(fy));
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                listener.onError();
            }
        });
        mQueue.add(stringRequest);
    }
    public String fanyiToString(fanyi fy){
        //处理解析后的json数据,转成UI输出的字符串
        String strexplain = "解释:";
        String strphonetic = "发音:";
        String strweb = "网络释义:";
        if (fy.basic == null){return "你所查找的还没有准确翻译";}
        for (int i = 0; i<fy.basic.explains.length; i++){
            strexplain +=fy.basic.explains[i]+"\n";
            if (i != fy.basic.explains.length-1 )
            {strexplain +="\t\t\t\t";}
        }
        strphonetic += fy.basic.phonetic +"\n";
        for (int i = 0; i<fy.web.size(); i++){
            for(int j = 0; j<fy.web.get(i).value.length;j++)
            {
                strweb += fy.web.get(i).value[j]+",";
            }
            strweb += fy.web.get(i).key+"\n";
            strweb += "\t\t\t\t\t\t\t";
        }
        return strexplain+"\n"+strphonetic+"\n"+strweb;
    }
}

最后是Presenter层:
这里面是CidianPresenter类和它的接口,因为Model层里面有异步线程操作网络数据,所以需要一个监听器接口onfanyiListener来实现监听器,让异步线程的结果回调。
onfanyiListener.java

package scut.cidian.Presenter;

import scut.cidian.Model.fanyi;

/**
 * Created by yany on 2016/4/6.
 */
public interface onfanyiListener {
    //成功时的回调
    void onSuccess(String str);
    //失败时候的回调
    void onError();
}

ICidianPresenter.java

package scut.cidian.Presenter;

import android.content.Context;

/**
 * Created by yany on 2016/4/5.
 */
public interface ICidianPresenter {
    //将View层获得的数据传入Model层
    void InputToModel(String input,Context context);
}

CidianPresenter.java

package scut.cidian.Presenter;

import android.content.Context;

import scut.cidian.Model.fanyi;
import scut.cidian.Model.fanyimodel;
import scut.cidian.View.fanyiView;

/**
 * Created by yany on 2016/4/5.
 */
public class CidianPresenter implements onfanyiListener {
    private fanyimodel fanyimodel;
    private fanyi fy;
    private fanyiView fyV;

    public  CidianPresenter(fanyiView fyV){
        this.fyV = fyV;
        fanyimodel = new fanyimodel();
    }//重构函数,初始化View接口实例和model实例

    public void ModelGetData(String input, Context context){
        fanyimodel.HandleData(input, context, this);
    }//将View层获得的数据传入Model层


    public void onSuccess(String str) {
        fyV.SetInfo(str);
    }//回调函数,调用UI更新

    @Override
    public void onError() {
        fyV.SetError();
    }//回调函数,调用UI输出出错信息
}

CidianPresenter实现了通过View接口实例和model实例,连接了两个模块。

结果:

我的demo下载地址

参考:
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0313/2599.html
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0313/2599.html