架构
前言
MVC代码下载地址MVCDemo
MVP代码下载地址MVPDemo
相信从事软件开发的伙计们肯定熟悉或者听说过项目架构,比如要新开发一个APP或者Web项目,首先考虑的就是项目需要设计什么样的架构,MVC还是MVP呢?MVC和MVP具体是怎么体现的,有哪些优点,哪些缺点呢?
为什么需要架构设计
假如我们不需要架构设计,那我们的APP项目代码会写成什么样,会不会Activity里有加载View,初始化View的代码;
如果需要跟服务器的交流,那也会有网络操作代码;如果有需要从本地数据库存取数据,那么也会有数据库操作代码;
如果有一些操作大家都需要调用到,比如字符串操作,图片操作,内存操作,那么这个activity有一份这样的代码,那个activity也有这样一份代码
显然这种情况,如果一直是单人开发,且项目比较小,迭代周期慢,那问题不大,反正就一个人搞,挺好的;但是如果是团队合作开发,且是快速迭代的产品,那每个人面对这样的代码,怎么开发,怎么维护,那将是一个巨大的痛苦啊。
所以这时候就需要引入架构设计使得程序模块化或者说组件化,降低代码的耦合性,加入面向接口的开发,使得开发人员只需要专注自己的开发内容,更加利于后期维护,提高程序开发效率,也方便后续的测试及问题的定位
MVC
MVC全称Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范;它用一种业务逻辑,数据处理,界面显示相分离的方法去组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。
划分
在Android开发过程中,比较流行的项目框架曾经是MVC模式,采用这种模式的好处就是便于UI界面的显示和业务逻辑的分离,具体如下
- 模型层(Model)
这里一般就是业务逻辑真正的处理部分了,通常做一些数据处理工作,比如数据库操作,网络请求,复杂算法,耗时操作等,用来描述业务逻辑怎么组合,同时也为数据定义业务规则 - 视图层(View)
这里是数据显示的部分,界面就是各种UI组件(XML布局或者Java自定义控件对象),只负责展示数据,同时接收控制器传过来的结果 - 控制层(Controller)
这里通常就交给Activity和Fragment了,这里就会处理用户交互问题;比如视图发请求给控制器,由控制器来选择相应的模型来处理;模型返回结果给控制器,由控制器选择合适的视图展示
在Android中的理解
在Android的MVC中,View大多数情况是xml文件,我们通常对View所做的操作逻辑都是写在Activity或者Fragment中,比如点击事件,长按事件等,这样请求事件发送到Controller中,比如点击事件是下载,那么Controller就会将事件转发到Model层去请求数据,Model获取数据后就会通过消息订阅发布机制或者接口去更新View,更新View的操作也是在Activity或者Fragment中操作,这样一个闭环就形成了,View -> Controller -> Model - > View
但是这里View也是可以直接访问Model的,要知道我们自定义的View是Java类,在这个类里是可以直接进行访问Model操作获取数据的,就是绕开了Controller,也是一个闭环,View -> Model - > View
样例
说了这么多,那到底这种模式映射到Android开发中是什么样的呢?
我们就写一个简单的业务功能,情景是用户点击屏幕上的按钮,想要获取自己的用户信息,然后进行网络请求,获取数据后展示在屏幕上
GIF图展示
工程目录
编写View层
也就是XML文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_getuser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/get_usermsg"
android:textColor="@android:color/white"
android:textSize="@dimen/size_14"
android:paddingLeft="@dimen/paddind_8"
android:paddingRight="@dimen/paddind_8"
android:background="@color/colorAccent"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_20"/>
<TextView
android:id="@+id/tv_msg"
android:layout_below="@+id/btn_getuser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingLeft="@dimen/paddind_8"
android:paddingRight="@dimen/paddind_8"
android:layout_marginTop="@dimen/margin_20"/>
</RelativeLayout>
里面需要引用到资源文件diemens.xml,strings.xml,colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="margin_20">20dp</dimen>
<dimen name="size_14">14sp</dimen>
<dimen name="paddind_8">8dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
<resources>
<string name="get_usermsg">获取用户信息</string>
</resources>
编写Model层
需要一个对象封装用户信息
/**
* @Description TODO(封装用户信息)
* @author mango
* @Date 2018/10/25 10:04
*/
public class User {
private String userId;
private String userName;
private String age;
private String sex;
private String job;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
我们这里使用面向接口方式来开发网络请求操作,先定义一个接口
/**
* @Description TODO(关于用户数据操作接口)
* @author cxy
* @Date 2018/10/25 10:08
*/
public interface UserModel {
/**
* @Description TODO(获取用户数据)
* @author cxy
* @parame userId 用户索引
* @parame listener 回调接口
*/
void getUserMsg(String userId , onUserListener listener);
}
然后定义一个回调接口
/**
* @Description TODO(用户操作回调接口)
* @author cxy
* @Date 2018/10/25 10:12
*/
public interface onUserListener {
public static final int USERID_IS_EMPTY = 1;
public static final int USER_IS_INVALID = 2;
void onBefore();
void onSuccess(User user);
void onError(int errorID);
}
接口实现类
/**
* @Description TODO(用户操作具体实现类)
* @author cxy
* @Date 2018/10/25 10:20
*/
public class UserModelImpl implements UserModel {
@Override
public void getUserMsg(String userId, final onUserListener listener) {
if (TextUtils.isEmpty(userId)) {
listener.onError(USERID_IS_EMPTY);
return;
}
if (listener == null) return;
listener.onBefore();
//这里模拟网络耗时操作,实际开发中绝对不要这样直接new Thread
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
User user = new User();
user.setUserId("12");
user.setUserName("凯撒");
user.setAge("35");
user.setSex("0");
user.setJob("码农");
if (user == null) {
listener.onError(USER_IS_INVALID);
} else {
listener.onSuccess(user);
}
}
}).start();
}
}
这里有人可能会说为什么要定义接口做呢,这么麻烦,干脆写个公共的网络操作类,提供一个getUserMsg方法调用就行了;为什么这种方式不可取呢,听我慢慢道来
假如这里网络操作是用原生HttpUrlConnection开发的,然后哪天需要改成OKHttp,难道去修改getUserMsg方法;就算改完后,假如哪天又改成了Retrofit去实现网络操作,难道又去修改getUserMsg方法;这样一连串的迭代,方法被改了多少遍,原来的代码不复存在,假如哪天需要用到以前的,那又得重新编写,这不是遭罪吗
可能有的朋友说,那就写多个getUserMsg方法,面临的问题就是一个类里写了太多这种同一种功能方法,繁杂,对以后的维护不是一个好消息
所以这里就采用面向接口的开发,不管以后的迭代中改成什么需求;比如使用OKHttp请求,那就再定义一个类UserModelWithOkHttpImpl去实现上面的UserModel接口,因为接口是高度抽象的,只定义功能,具体实现因人而定;这样操作以后,不仅保留了以前的代码,而且实现类简洁易维护
编写Controller
public class MainActivity extends AppCompatActivity implements View.OnClickListener, onUserListener {
private String TAG = MainActivity.class.getSimpleName();
private final int REQUEST_SUCCESS = 1;
private final int REQUEST_ERROR = 2;
private String userID = "12";
private ProgressDialog dialog;
private Button btn_getuser;
private TextView tv_msg;
private UserModel userModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
finView();
initListener();
}
private void finView() {
btn_getuser = (Button) findViewById(R.id.btn_getuser);
tv_msg = (TextView) findViewById(R.id.tv_msg);
}
private void initListener() {
btn_getuser.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (userModel == null) {
userModel = new UserModelImpl();
}
userModel.getUserMsg(userID,this);
}
@Override
public void onBefore() {
if (dialog == null) {
dialog = new ProgressDialog(this);
}
dialog.show();
}
/**
* 这是是在子线程回调,不能直接更新View
* @param user
*/
@Override
public void onSuccess(User user) {
Message message = handler.obtainMessage();
message.obj = user;
message.what = REQUEST_SUCCESS;
handler.sendMessage(message);
}
/**
* 这是是在子线程回调,不能直接更新View
* @param errorID
*/
@Override
public void onError(int errorID) {
handler.sendEmptyMessage(REQUEST_ERROR);
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
switch (what) {
case REQUEST_SUCCESS:
User user = (User) msg.obj;
tv_msg.append("姓名:"+user.getUserName()+"\n");
tv_msg.append("年龄:"+user.getAge()+"\n");
tv_msg.append("职业:"+user.getJob());
dialog.dismiss();
break;
case REQUEST_ERROR:
tv_msg.setText("");
dialog.dismiss();
break;
}
}
};
}
这里逻辑挺简单的
- XML是属于View层,对它的操作放在了Activity
- 然而Activity作为Controller层,持有UserModel引用(UserModel 作为Model层),接着点击按钮请求用户数据,这样Controller层就向Model层发起了请求
- 然后Model层进行网络操作,因为可能耗时,所以在真正执行网络前通过回调,通知View层显示加载的dialog,然后获取数据通过回调返回给View层显示出来
就这样一个完整的闭环操作结束了
优点
- 代码耦合性低,减小模块之间的相互影响;比如同一个数据模型,你可以用柱形图来展示,也可以用圆形图展示,也就是说修改View层的显示效果不用修改数据
- 可扩展性好,模块之间影响小,加上面向接口的开发,当你新增一个功能或者新增一种功能实现的时候,只需要定义接口和实现接口,就不需要修改以前的代码
- 模块职责划分明确,如上面所说,每个模块做自己的事
- 代码维护性好,修改模型不会影响到视图,反过来,修改视图,也不会影响到模型
缺点
MVC其实也有自己的不足,要不然也不会有后来的MVP了
- 增加了系统结构和实现的复杂性,对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率
- 视图与控制器间的过于紧密的连接,也就是耦合性高,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用
写这么多了,来,看张图休息会
MVP
通过上面的例子可以看出来,在MVC中,XML文件作为View层,能做的事实在是有限,比如你想在运行时改变下颜色,字体大小,或者背景啥的,几乎没办法通过修改XML文件去做到,可能有的人会说可以通过修改XML文件字节码,那这就属于杠精了;Activity作为Controller层,做了太多事,首先要加载应用布局,然后初始化界面,View的操作,用户逻辑基本上都在Activity开发
就这样View层的相关操作几乎都放到了Controller层去做,导致Activity的代码非常拥挤,并不是一个标准的MVC模型中的Controller;我之前接手的项目,一个Activity的代码两三千行,逻辑混乱,不管开发还是维护那是巨痛苦的事;如果研究Android源码的同学可能就觉得一个类才两千行代码不奇怪,要知道ViewRootImpl类的performTraversals方法都快一千行了,别说类了,RecyclerView都快上万行了
而且MVC模型中还有一个比较重要的问题就是Model层和View层是可以直接交流的,这就导致两者之间会存在耦合关系;还有Activity即属于Controller,又属于View,那这两层也产生了耦合;对于一个成熟的项目来说,耦合是非常致命的,不管后期开发,维护还是测试都很难继续下去
经过种种探索,MVP就应运而生了
MVP全称Model-View-Presenter,它可以说是MVC的一种进化版本,解决了MVC的不少痛点,不过还是有一定的相似性
划分
- Model:这里跟MVC中的Model基本一样
- View:这里就跟MVC中的View不太一样了,不仅仅是XML文件,还包括Activity,Fragment,自定义View等java文件,这样Activity等就可以只负责View的绘制,显示,响应用户的操作
- Presenter:这就是重点了,作为View与Model之间联系的纽带,让View和Model解耦;同时它与View层是通过接口通信,这又与View层解耦;将原来Activity属于Controller的操作移到了Presenter中
与MVC区别
- MVP中View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
- 通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
- Presenter与View的交互是通过接口来进行的,更有利于添加单元测试;而MVC中Activity包含了View和Controller代码,很难做测试
在Android中的理解
在Android中的MVP,XML,Activity,Fragment等都是属于View层,不在执行具体的逻辑,只是纯粹的操作View;具体逻辑交给Presenter处理,View与Presenter通过接口通信,达到解耦;Presenter收到View层请求后转发给相应的Model层,Model层处理完数据后将数据返回给Presenter,然后Presenter再通知View层更新View
样例
现在以一个用户登录的需求为例进行讲解
注意,MVP的核心思想就是一切皆为接口,把Activity中的UI逻辑抽象成View接口,把业务逻辑抽象成Presenter接口,把数据操作抽象成Model接口
GIF样例
工程目录结构
编写View层
首先是xml,很简单 两个输入框 一个按钮,一个提示的textview
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/et_name"
android:layout_centerHorizontal="true"
android:layout_width="@dimen/length_200"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_20"
android:hint="@string/hint_name"
android:textSize="@dimen/size_14"/>
<EditText
android:id="@+id/et_pw"
android:layout_below="@+id/et_name"
android:layout_centerHorizontal="true"
android:layout_width="@dimen/length_200"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_20"
android:hint="@string/hint_pw"
android:textSize="@dimen/size_14"/>
<Button
android:id="@+id/btn_login"
android:layout_below="@+id/et_pw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/margin_20"
android:text="@string/login"
android:textSize="@dimen/size_14"
android:textColor="@android:color/white"
android:background="@color/colorAccent"/>
<TextView
android:id="@+id/tv_hint"
android:layout_below="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_20"
android:gravity="center"
android:textSize="@dimen/size_14"/>
</RelativeLayout>
用到的资源文件如下
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="length_200">200dp</dimen>
<dimen name="margin_20">20dp</dimen>
<dimen name="size_14">14dp</dimen>
</resources>
<resources>
<string name="hint_name">请输入用户名</string>
<string name="hint_pw">请输入密码</string>
<string name="login">登 录</string>
<string name="login_success">登录成功</string>
<string name="login_fail">登录失败</string>
<string name="username_isempty">用户名不能为空</string>
<string name="userpw_isempty">密码不能为空</string>
</resources>
/**
* @Description TODO(由View层实现,主要是响应presenter层的操作)
* @author cxy
* @Date 2018/10/25 17:15
*/
public interface ILoginView extends OnProgressListener {
/**
* @Description TODO(将View中数据反馈给presenter)
* @author cxy
*/
User getUserMsg();
/**
* @Description TODO(清除edittext数据)
* @author cxy
*/
void clearEdit();
/**
* @Description TODO(将登录数据返回给View)
* @author cxy
* @parame result 登录返回数据
*/
void setLoginResult(String result);
}
这就是把UI逻辑抽象成了接口,由Activity去实现
/**
* @Description TODO(显示进度条 隐藏进度的接口,统一定义,避免每个需要进度显示的接口重复定义这些方法)
* @author cxy
* @Date 2018/10/25 17:07
*/
public interface OnProgressListener {
void onShowProgress();
void onHideProgress();
}
还有Activity的代码最后放出来,现在放出来有点突然
编写Model层
先定义接口,Model层既然要处理网络操作,那肯定要有网络请求方法,先定义出来,具体谁实现,怎么实现,那是实现的人的事
/**
* @Description TODO(由相应的model实现,关于用户数据操作接口)
* @author cxy
* @Date 2018/10/25 16:55
*/
public interface IUserModel {
/**
* @Description TODO(提供网络请求操作)
* @author cxy
* @parame url 网络请求链接
* @parame resultListener 请求回调接口
*/
void onHttpRequest(String url, OnResultListener resultListener);
}
然后需要去实现
/**
* @Description TODO(网络请求具体实现类)
* @author cxy
* @Date 2018/10/25 17:02
*/
public class UserModelImpl implements IUserModel{
/**
* @Description TODO(实现具体的登录逻辑)
* @author cxy
* @Date 2018/10/25 17:09
*/
@Override
public void onHttpRequest(final String url, final OnResultListener listener) {
if (listener == null) throw new NullPointerException("OnResultListener is null,this is illegal");
if (TextUtils.isEmpty(url)) throw new NullPointerException("url is null,this is illegal");
//这里模拟网络耗时操作,实际开发中绝对不要这样直接new Thread
new Thread(new Runnable() {
@Override
public void run() {
String result = "";
//接下来就是网络操作 进行登录 获取登录结果
try {
Thread.sleep(2000);
result = "success";
} catch (InterruptedException e) {
e.printStackTrace();
}
if (TextUtils.isEmpty(result)) {
listener.onError();
} else {
listener.onSuccess();
}
}
}).start();
}
}
编写Presenter层
也是先定义业务逻辑接口,既然登录操作,那就定义就完事了
/**
* @Description TODO(由相应presenter实现,登录操作对应的presenter,连接View和model)
* @author cxy
* @Date 2018/10/25 17:03
*/
public interface ILoginPresenter {
/**
* @Description TODO(登录中转点,由View层调用,在该方法中将请求转给model)
* @author cxy
* @Date 2018/10/25 17:12
*/
void onLogin();
}
由相应的presenter实现
/**
* @Description TODO(登录presenter对应的具体实现类)
* @author cxy
* @Date 2018/10/25 17:12
*/
public class LoginPresenter implements ILoginPresenter, OnResultListener {
private final int REQUEST_SUCCESS = 1;
private final int REQUEST_ERROR = 2;
private ILoginView loginView;
private IUserModel userModel;
private WeakReference<Context> mContext ;
public LoginPresenter(Context context,ILoginView loginView) {
this.loginView = loginView;
userModel = new UserModelImpl();
mContext = new WeakReference<>(context);
}
@Override
public void onLogin() {
loginView.onShowProgress();
User user = loginView.getUserMsg();
if (StringUtils.isEmpty(user.getUserName(),user.getPw()) ) {
handler.sendEmptyMessage(REQUEST_ERROR);
return;
}
String url ;
//接下来进行登录请求链接的组装
url = "www.baidu.com";
userModel.onHttpRequest(url,this);
}
@Override
public void onSuccess() {
handler.sendEmptyMessage(REQUEST_SUCCESS);
}
@Override
public void onError() {
handler.sendEmptyMessage(REQUEST_ERROR);
}
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
int what = msg.what;
switch (what) {
case REQUEST_SUCCESS:
loginView.onHideProgress();
loginView.setLoginResult(mContext.get().getResources().getString(R.string.login_success));
break;
case REQUEST_ERROR:
loginView.onHideProgress();
loginView.clearEdit();
loginView.setLoginResult(mContext.get().getResources().getString(R.string.login_fail));
break;
}
}
};
}
/**
* @Description TODO(操作结果回调)
* @author cxy
* @Date 2018/10/25 18:38
*/
public interface OnResultListener {
void onSuccess();
void onError();
}
可以看到这里持有Model层和View层接口的引用,通过接口获取View层相关数据及控制View的变化,然后通过接口去调用Model层相关方法,最后又通过View层接口将结果返回
还有View层最后一个Activity
public class MainActivity extends AppCompatActivity implements View.OnClickListener, ILoginView {
private EditText et_name;
private EditText et_pw;
private Button btn_login;
private TextView tv_hint;
private ProgressDialog dialog;
private ILoginPresenter loginPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
initListener();
}
private void findView() {
et_name = (EditText) findViewById(R.id.et_name);
et_pw = (EditText) findViewById(R.id.et_pw);
btn_login = (Button) findViewById(R.id.btn_login);
tv_hint = (TextView) findViewById(R.id.tv_hint);
}
private void initListener() {
btn_login.setOnClickListener(this);
}
@Override
public void onClick(View v) {
if (loginPresenter == null) {
loginPresenter = new LoginPresenter(this,this);
}
if (dialog == null) {
dialog = new ProgressDialog(this);
}
if (TextUtils.isEmpty(et_name.getText().toString())) {
T.show(this, getResources().getString(R.string.username_isempty));
return;
}
if (TextUtils.isEmpty(et_pw.getText().toString())) {
T.show(this, getResources().getString(R.string.userpw_isempty));
return;
}
loginPresenter.onLogin();
}
@Override
public void onShowProgress() {
dialog.show();
}
@Override
public void onHideProgress() {
dialog.dismiss();
}
@Override
public User getUserMsg() {
User user = new User();
user.setUserName(et_name.getText().toString());
user.setPw(et_pw.getText().toString());
return user;
}
@Override
public void clearEdit() {
et_name.setText("");
et_pw.setText("");
}
@Override
public void setLoginResult(String result) {
tv_hint.setText(result);
}
}
可以看到,在MainActivity中,只是纯粹的做一些View相关的工作,没有任何业务逻辑,所有用户操作全部转交给presenter去干,这里只是用户点击登录按钮这个业务逻辑,实际上,用户的触屏操作多了去了,比如点击按钮刷新,删除跳转到另外一个页面等,这些所有逻辑都可以在presenter接口中定义相应的方法,然后Activity中去调用就行了
公共类
/**
* @Description TODO(字符串操作公用类)
* @author cxy
* @Date 2018/10/25 19:02
*/
public class StringUtils {
/**
* @Description TODO(判断字符串是否为empty)
* @author cxy
* @return true 有一个为empty就为true , false 都不为empty就为false
* @parame 接收一个字符串数组
*/
public static boolean isEmpty(String... values){
boolean isEmpty = false;
for(int i=0; i<values.length; i++){
if (TextUtils.isEmpty(values[i])) {
isEmpty = true;
break;
}
}
return isEmpty;
}
}
/**
* @Description TODO(提示公用类)
* @author cxy
* @Date 2018/10/25 19:10
*/
public class T {
public static void show(Context context,String hint){
Toast.makeText(context,hint,Toast.LENGTH_SHORT).show();
}
}
通过接口操作后,可以发现View,Model,Presenter三者可以单独工作
没了View,Presenter依然可以工作,在单元测试的时候,比如想测试Presenter中的业务逻辑是否正确,只需要写个脚本模拟Activity,然后在相应的方法中提供数据,最后调用Presenter接口的login方法,看能否得到预期结果,而且两者完成解耦;而且Presenter只专注于业务逻辑,至于页面是什么样的,不关心,所以同一个Presenter可以对应多个View;
去掉Presenter,Model依然可以独立工作,只需要给脚本实现回调接口,然后调用Model接口,传递参数,调用相应的数据操作方法就可以测试Model是否能正常工作,也能实现解耦
用一张图表示MVP
优点
- 几乎所有的思想都是为了解耦,更加利于维护和开发,把大工程化整为零,每个团队负责一小部分,相互独立
- 从上面的样例也可以看出,这种模式下,Activity等View层类的代码相当简洁,基本上只有对View的操作;还有想了解一个模块有哪些业务,就看这个模块的Presenter接口就行了,不管是定位代码还是后续修改业务都很方便
- 由于面向接口的开发,非常便于单元测试
- 代码复用程度提高,一个Presenter可以适用于多个View
缺点
- 从上面的例子和工程目录图明显可以看出来,使用MVP后,类的数量能增加一倍以上,也给维护工作增加了难度,这应该是最大的一个缺点了
- 维护接口的难度并不低,特别是业务逻辑越来越复杂以后,维护工作更难
- Presenter有很多连接View与Model之间的操作,造成Presenter的臃肿
- Presenter和View通过接口通信太繁琐,一旦View层需要的数据变化,那么对应的接口就需要更改
总结
不管是MVC还是MVP都有自己的优点和缺点,没有哪个项目是绝对的适合MVC或者适合MVP,有时候你的项目可能是两者相结合的情况,并且对于使用MVP带来的类数量暴增的情况,需要想办法缓解;可以通过Template自动生成需要的类和接口,这样少去了频繁的复制粘贴;对于一些简单逻辑的页面,可以不使用MVP,直接用MVC;如果有一些页面业务逻辑差不多,可以复用一些Presenter;还可以通过封装基本类,将一些公用方法提取到父类,避免每个接口都去定义这些;或者使用一些第三方框架,比如像支付宝团队使用的TheMVP框架,是通过将Activity或Fragment作为Presenter,将UI操作抽到Delegate中,作为View层;还有一个是通过第三方的消息订阅发布框架或者Handler进行消息通信,代替过多的接口通信,这也能减少不少的类;具体怎么合适,需要自己在实践中多尝试
对于新手来说,刚做项目从零开始,就不要想太多设计架构的问题了,先做出来才是王道,后续再考虑重构的问题,因为所有的优化都是基于你对这些现有模块都很熟悉的情况,如果一开始就想这些,可是都不知道要做成什么样,那什么时候才能写完项目
终于结束了