吾日三省吾身,为人谋而不忠乎?与朋友交而不信乎?传不习乎?
问题
上一个桌面项目MVC模式,在主activity中的代码超过了2000行,完成后感觉自己的项目很烂。虽然逻辑还算清晰,但是整个View层和Controller层感觉太臃肿了。
是时候该从MVC转到MVP模式了。下面就有我来简单的总结一点心得,介绍一下MVP模式,希望能给想用MVP的人一点帮助。
MVP总结和介绍
在MVP模式里通常包含4个要素:
(1)View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity);
(2)View interface:需要View实现的接口,View通过View interface与Presenter进行交互,降低耦合,方便进行单元测试;
(3)Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合);
(4)Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑。
MVC与MVP模式比较
MVC模式
- M : 业务层和模型层,相当与javabean和我们的业务请求代码
- V : 视图层,对应Android的layout.xml布局文件
C : 控制层,对应于Activity中对于UI 的各种操作
MVP模式
M :还是业务层和模型层
- V : 视图层的责任由Activity来担当
- P : 新成员Prensenter 用来代理 C(control) 控制层
MVPDemo讲解
下面我将用一个比较简单的Demo来向大家展示一下MVP,感受一下朵密的力量吧!
这里先来看一下biz,也就是业务层,业务层独立出去,该在哪儿调用就在哪儿调用。
RequestForDataBiz代码如下:
/**
* Created by Administrator on 2017/2/25 0025.
* 一个请求数据的biz
* biz就是业务层的意思
*/
public interface RequestForDataBiz {
//请求数据业务
void requestForData(OnRequestListener listener);
}
数据请求的回掉接口,声明了成功和失败的方法 。OnRequestListener代码如下:
/**
* Created by Administrator on 2017/2/25 0025.
*/
/*请求成功或者失败的回调接口,就和网络请求一样,一个网络请求的回调大致
有四个success,finish error cancel.这里就简单用两个,请求返回的数据为一个String集合*/
public interface OnRequestListener {
void onSuccess(List<String> data);
void onFailed();
}
RequestForDataBizIml代码如下:
请求的实现类为了模拟网络请求,开启了一个会sleep1秒的线程,然后装填请求的数据,通过OnRequestListener 接口回调出去,与我们平时开发的方式一致。
/**
* Created by Administrator on 2017/2/25 0025.
* 底下这是一个完整的网络请求,就是模拟网络数据太麻烦了,弄个假的简单说明
*/
public class RequestForDataBizIml implements RequestForDataBiz {
@Override
public void requestForData(final OnRequestListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
ArrayList<String> data = new ArrayList<String>();
for (int i = 1; i < 12; i++) {
data.add("item" + i);
}
if (null != listener) {
listener.onSuccess(data);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
// public static String doGet(Context context, String url, String params) //
// throws IOException {
// InputStream in = null;
// URL realUrl = new URL(url.replace(" ", "%20"));
// Log.e("infodoGet", "HttpUtil:doGet realUrl=" + realUrl.toString());
// URLConnection conn = realUrl.openConnection();
// conn.setConnectTimeout(5000);
// conn.setReadTimeout(5000);
// conn.setRequestProperty("accept", "*/*");
// conn.setRequestProperty("connection", "Keep-Alive");
// conn.setRequestProperty("user-agent",
// "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)") ;
// // conn.setRequestProperty("X-Bsl-Client" , Configure.PACKAGE_NAME + "^" + Configure.has_secure) ;
//
// try {
// conn.connect();
// in = conn.getInputStream();
// BufferedReader br = new BufferedReader(new InputStreamReader(in));
// String line = "";
// result = new StringBuffer();
// while (null != (line = br.readLine())) {
// result.append(line);
// }
// } catch (SocketException e) {
// return new String("SocketException");
// } catch (SocketTimeoutException e) {
// return new String("SocketException");
// }
//
// try {
// if (in != null) {
// in.close();
// }
// } catch (IOException ex) {
// ex.printStackTrace();
// }
// Log.e("infodoGet", "Httpresult.toString()"+ result.toString());
// return result.toString();
// }
//
}
业务层的业务在此处完成。
MVP的凝视与思考
由于Activity变成了view层不再去控制界面,但是具体的界面的改变api其实还是由Activity来提供的,所以在写MVP之前需要思考,View层需要哪些方法,要做哪些事情。
- 1,显示loading
- 2,隐藏loading
- 3,listview的初始化,展现页面
- 4,弹出Toast消息
MVPView接口代码如下
/**
* Created by Administrator on 2017/2/25 0025.
* view层需要哪些方法,涉及到的UI展示。
*/
public interface MVPView {
//显示loading progressBar
void showLoading();
//隐藏loading progressBar
void hideLoading();
//ListView的初始化,展示界面
void initListView(List<String> data);
//Toast message
void showMessage(String message);
}
我们总结出View层需要的接口。我们的Activty就是View层,所以直接用Activity来实现上面的方法。将View视图层就完成。
public class MainActivity extends AppCompatActivity implements MVPView {
private ProgressBar mMvpLoadingbarProgressBar;
private ListView mMvpListviewListView;
private RelativeLayout mActivityMainRelativeLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMvpLoadingbarProgressBar = (ProgressBar) findViewById(R.id.mvp_loadingbar);
mMvpListviewListView = (ListView) findViewById(R.id.mvp_listview);
mActivityMainRelativeLayout = (RelativeLayout) findViewById(R.id.activity_main);
}
@Override
public void showLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.GONE);
}
@Override
public void initListView(List<String> data) {
ArrayAdapter adapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,data);
mMvpListviewListView.setAdapter(adapter);
}
@Override
public void showMessage(String message) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
}
View视图层完成了。接下来开始写presenter层, 同样在写presenter之前想想控制层需要哪些方法?
- (1)网络请求数据
- (2)点击事件的响应
MVPresenter代码如下
/**
* Created by Administrator on 2017/2/25 0025.
* - (1)网络请求数据
- (2)点击事件的响应
*/
public class MVPresenter {
private MVPView mvpView;
RequestForDataBiz requestBiz;
private Handler mHandler;
public MVPresenter(MVPView mvpView) {
this.mvpView = mvpView;
requestBiz = new RequestForDataBizIml();
mHandler = new Handler(Looper.getMainLooper());
}
public void onResume(){
mvpView.showLoading();
requestBiz.requestForData(new OnRequestListener() {
@Override
public void onSuccess(final List<String> data) {
//由于请求开启了新线程,所以用handler去更新界面
mHandler.post(new Runnable() {
@Override
public void run() {
mvpView.hideLoading();
mvpView.initListView(data);
}
});
}
@Override
public void onFailed() {
mvpView.showMessage("请求失败");
}
});
}
public void onItemClick(int position){
mvpView.showMessage("点击了item"+position);
}
public void onDestroy(){
mvpView = null;
}
}
Presenter完成(Presenter里面像不像接管了MVC模式中的C层?理解才是硬道理啊!),现在就剩下一件事,Activity中使用Presenter。
下面放大招:完整版的MainActivity:
/*
* MVP实现了,是不是很简洁?
* 就是这么清爽,没有乱七八糟的业务。
* 没有各种点击处理逻辑,Activity只需要提供View层的方法就可以了。
* 你看现在activity的生命周期都托管给了MVPresenter。
* 你要做的就是加深理解,把你的业务逻辑也带入到prenter层去处理。
* */
public class MainActivity extends AppCompatActivity implements MVPView ,AdapterView.OnItemClickListener{
private ProgressBar mMvpLoadingbarProgressBar;
private ListView mMvpListviewListView;
private RelativeLayout mActivityMainRelativeLayout;
MVPresenter mMVPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMvpLoadingbarProgressBar = (ProgressBar) findViewById(R.id.mvp_loadingbar);
mMvpListviewListView = (ListView) findViewById(R.id.mvp_listview);
mActivityMainRelativeLayout = (RelativeLayout) findViewById(R.id.activity_main);
mMVPresenter=new MVPresenter(this);
}
@Override
public void showLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mMvpLoadingbarProgressBar.setVisibility(View.GONE);
}
@Override
public void initListView(List<String> data) {
ArrayAdapter adapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,data);
mMvpListviewListView.setAdapter(adapter);
}
@Override
public void showMessage(String message) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mMVPresenter.onItemClick(position);
}
//将生命周期托付给MVPresenter,你的逻辑,业务,在这里面做的事都可以转移到MVPresenter
@Override
protected void onResume() {
super.onResume();
mMVPresenter.onResume();
}
@Override
protected void onDestroy() {
mMVPresenter.onDestroy();
super.onDestroy();
}
}
一点体会和小小的心得
可以看到,View只负责处理与用户进行交互,并把数据相关的逻辑操作都扔给了Presenter去做。视图层与控制层完全分离,可以让我们在界面还是很粗糙的情况下,先进行控制层的开发,甚至可以先让View层先提供方法出来,这样可以节省很多时间。