对于 MVP 应用架构的理解及其优化改造

MVP 架构

MVP架构由MVC发展而来。在MVP中,M代表Model,V代表View,P代表Presenter。

  • Model 负责获取数据,数据的来源可以是网络或本地数据库等;
  • View 负责界面数据的展示,与用户进行交互;
  • Presenter 是Model与View之间的通信的桥梁,将Model与View分离开来,也是业务进行实现的地方;

这三个层面之间的关系如下:
MVP 应用架构图

我们先简单用一个简单的小流程做说明:
用户触碰界面触发事件,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 的优点:

  1. 大量业务代码放到 Presenter,大大降低了 Activity 的复杂度
  2. 隐藏数据,数据交由 Model 层处理,这一层对于 View 层完全透明;
  3. Presenter 可以复用,一个 Presenter 可以用于多个 View。
  4. 模块职责划分明显,层次清晰;

但与此同时,MVP 的这种模式同时也会带来很多缺点,其中对大的就是

  • 代码阅读性较差,增加了应用维护成本
  • 极大的增加了接口文件的数量

举个例子来说,当我需要修改一个功能时,我首先从界面上找到出现问题的位置,然后在执行的方法中查看其执行的代码,但是在 MVP 结构中,View 层仅仅保存了 Presenter 的接口,如果直接点击某些方法,那么将跟进该 Presenter 接口文件中,如何这个时候有若干的实现类,那么将会很麻烦,就必须在 View 中去找到 Presenter 的实现类。这些都将会增加使用 MVP 架构的代码的维护成本。

另外一个要注意的是,在 MVP 模型中,View 和 Presenter 层之间相互引用其基类或其本身,在必要时其将调用对方的方法。虽然这么做也能将这两层隔离开,但是这样的隔离完全依赖于接口,这样会造成接口文件数量大大增加。而在真正开发的情况下,并非每种功能都需要复杂到使用接口来定义。而接口的增多又会增加程序的维护成本。比如跟代码时很容易跟到接口文件,却很难找到具体功能的实现代码。

改进

首先要注意的是 View 与 Presenter 分离的原则必须保持,而在此基础上,只要减少接口的定义和双方的引用就可以降低维护成本。那么这样的话,MVP 的架构将变成这样:
改进后的 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
      • presenter
      • view

猜你喜欢

转载自blog.csdn.net/Lee_Swifter/article/details/104717081
今日推荐