MVP 架构
MVP架构由MVC发展而来。在MVP中,M代表Model,V代表View,P代表Presenter。
- Model 负责获取数据,数据的来源可以是网络或本地数据库等;
- View 负责界面数据的展示,与用户进行交互;
- Presenter 是Model与View之间的通信的桥梁,将Model与View分离开来,也是业务进行实现的地方;
这三个层面之间的关系如下:
我们先简单用一个简单的小流程做说明:
用户触碰界面触发事件,View层把事件通知Presenter层,Presenter层通知Model层处理这个事件,Model层处理后把结果发送到Presenter层,Presenter层再通知View层,最后View层做出改变,这是一整套流程。
MVP 例子
为了更好得说明 MVP,我们简单的举一个 MVP 例子。
Model 层
Model 层实际就是数据层,这一层可以从网络、数据库或文件系统上获取一些二进制或是 JSON 格式的内容,而将数据传递给 Presenter 层时,就应该被解析为 JavaBean。
JavaBean
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
Model 主要处理数据的读写,JavaBean 和原生数据之间的转换。定义接口如下:
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 层
这一层是功能处理的主要代码所在,一般来说,在一个应用会有很多功能,每一个功能都应该有一个 Presenter 来处理其功能。
public class UserPresenter {
private IUserView mUserView; //注意此处对 View 层的引用
private IUserModel mUserModel; //对 Model 层的引用
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());
}
}
对于 Presenter 层,一般情况下也会有接口来对其功能进行规范。
public interface UserPresenterI {
public void saveUser( int id, String firstName, String lastName)
public void loadUser( int id)
}
View 层
View 层是用来向用户展示的界面,首先也要对 View 层使用接口规范:
public interface IUserView {
int getID();
String getFristName();
String getLastName();
void setFirstName(String firstName);
void setLastName(String lastName);
}
对于其实现类一般就是 Activity 或是 Fragment:
public class MainActivity extends Activity implements OnClickListener,IUserView {
UserPresenterI 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); //注意此处对 Presenter 的引用
}
@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);
}
}
这样,View 层仅仅处理界面上的内容,对于具体的业务逻辑,由于 View 层引用了 Presenter 的接口,因此可以调用 Presenter 的方法。而 Presenter 的实现中也引用了 View 层的接口,因此在业务上或回调上可以直接调用 View 接口的方法而使界面发生改变。
MVP 架构的优缺点
从上面的例子中可以总结出 MVP 的优点:
- 大量业务代码放到 Presenter,大大降低了 Activity 的复杂度
- 隐藏数据,数据交由 Model 层处理,这一层对于 View 层完全透明;
- Presenter 可以复用,一个 Presenter 可以用于多个 View。
- 模块职责划分明显,层次清晰;
但与此同时,MVP 的这种模式同时也会带来很多缺点,其中对大的就是
- 代码阅读性较差,增加了应用维护成本
- 极大的增加了接口文件的数量
举个例子来说,当我需要修改一个功能时,我首先从界面上找到出现问题的位置,然后在执行的方法中查看其执行的代码,但是在 MVP 结构中,View 层仅仅保存了 Presenter 的接口,如果直接点击某些方法,那么将跟进该 Presenter 接口文件中,如何这个时候有若干的实现类,那么将会很麻烦,就必须在 View 中去找到 Presenter 的实现类。这些都将会增加使用 MVP 架构的代码的维护成本。
另外一个要注意的是,在 MVP 模型中,View 和 Presenter 层之间相互引用其基类或其本身,在必要时其将调用对方的方法。虽然这么做也能将这两层隔离开,但是这样的隔离完全依赖于接口,这样会造成接口文件数量大大增加。而在真正开发的情况下,并非每种功能都需要复杂到使用接口来定义。而接口的增多又会增加程序的维护成本。比如跟代码时很容易跟到接口文件,却很难找到具体功能的实现代码。
改进
首先要注意的是 View 与 Presenter 分离的原则必须保持,而在此基础上,只要减少接口的定义和双方的引用就可以降低维护成本。那么这样的话,MVP 的架构将变成这样:
对于 MVP 模式,由于 Presenter 的很多功能都需要触发界面上的改变,因此其持有一个 View 层的接口,在必要时调用此方法。也正是因为这一个引用增加的 MVP 代码的复杂度。因此,要解决此问题,Presenter 必须要放弃对 View 接口的依赖,使其完完全全仅仅处理业务逻辑,与界面上的操作完全分隔开。
也就是说在上面 MVP 结构图中,需要将 View 与 Presenter 之间的双向引用改为单向引用。View 层持有 Presenter 的引用,反之则无。这样也使得 View 的接口定义变得没有必要,而 Presenter 是否需要使用接口则完全不依赖于代码结构,因此这将减少此模式中接口文件的数量。
不仅如此,在改进之后,也使得 Presenter 能够完全关注业务的逻辑,否则 Presenter 还需要在什么时候调用 View 的什么方法。使用抽象出来的
改进之后的问题
在这么做之后,由于没有了对 View 的引用,那么当界面需要发生改变时,应该通知界面就成了一个问题。这里有两个方法可以解决:
- 在 Presenter 中使用 setListener 的方式设置监听器,当逻辑需要界面发生改变时,将调用 Listener 中的接口
- View 层在调用 Presenter 的方法时,传入一个回调接口,在其中处理未来可能发生的界面改变。
示例
使用上述方法后,可以在没有引用 View 的接口的情况下完成 MVP 模式的所有功能。而实际上使用 Listener 也是类似于使用 View 接口,但是这种情况会更加通用,使得 Presenter 不必需要依赖 View 就能工作:
public class UserPresenter {
private XListener listener;
private IUserModel mUserModel; //对 Model 层的引用
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);
listener.onUserGot(user); //使用 Listener 来替代 IView
}
public void loadUser(int id, XListener onLoadListener) {
UserBean user = mUserModel.load(id);
onLoadListener.onUserGot(user); //使用方法上的实参来替代 IView
}
public interface XListener {
void onUserGot(User user)
}
}
代码结构
在使用了改进后的 MVP 模型后,代码将变成以功能为单位的模式,对于包的结构,可以使用如下方案:
- src
- 通用工具
- 网络基础库
- 数据解析
- …
- 通用UI组件
- 常用自定义View
- 常用的界面组件
- …
- 功能一
- model
- javabean
- presenter
- view
- activity
- fragment
- view
- …
- model
- 功能二
- model
- presenter
- view
- …
- 通用工具