在一个项目开始之前,开发者就应该根据项目的大小和内容决定用什么设计模式和框架,选好一个合适的设计模式,可以使项目开发分工更明确,效率更高效,出错率更低,维护更容易。
在我看来,设计模式就是一种把一个项目的代码分成若干个小块,让每个小块的耦合性尽量地降低,让每个模块的代码可重用性增强,让项目的维护更简单的方法。下面我们来学习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