MVP模式的优化

        在上一篇博客中,我们介绍了了MVP的基本使用步骤点击打开链接 ,但是还有许多问题等着我们做相应的优化。

问题 1:调用View可能会引发空指针异常

        在上面的例子中,MVP架构中的应用请求网络数据的时候,需要等待后台反馈数据后更新界面,但是在请求的过程中,可能当前的Activity因为某种原因销毁了.Presenter 收到后台反馈并调用View接口处理UI时,由于Activity已经被销毁,就会引发空指针异常。

       解决方案:

     之前是在Presenter的构造方法中得到View的引用,现在我们需要修改Presenter引用View的方式,来让View接口与宿主共存亡。

public class NewsPresenterImpl implements NewsPresenter, OnLoadNewsListListener {

    private static final String TAG = "NewsPresenterImpl";

    private NewsView mNewsView;
    private NewsModel mNewsModel;

    public NewsPresenterImpl(NewsView newsView) {
        this.mNewsModel = new NewsModelImpl();
    }


    /**
     * 绑定View
     *
     * @param
     */
    public void attachView(NewsView newsView) {
        mNewsView = newsView;
    }


    /**
     * 断开View
     *
     * @param
     */
    public void detachView() {
        mNewsView = null;
    }


}

相应的,在调用之前需要做一下非空判断

    @Override
    public void onSuccess(List list) {
        if (mNewsView != null) {
            mNewsView.hideProgress();
            mNewsView.addNews(list);
        }
       
    }

接下来,把绑定View的方法与View的生命周期关联起来。

   @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mNewsPresenter = new NewsPresenterImpl();
        ((NewsPresenterImpl)mNewsPresenter).attachView(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        ((NewsPresenterImpl)mNewsPresenter).detachView(this);
    }

问题 2:有太多的冗余代码

      在上面的demo中

public interface OnLoadNewsListListener {

    void onSuccess(List list);

    void onFailure(String msg, Exception e);
}

OnLoadNewsListListener 接口中的onSuccess()方法需要根据请求数据的不同类型设置为不同类型的参数,所以每当有新的数据类型都需要新建一个OnLoadNewsListListener接口。

     解决方案:引入泛型的概念,让调用者去定义具体想要接收的数据类型

public interface OnLoadNewsListListener {

    void onSuccess(T list);

    void onFailure(String msg, Exception e);
}

   BaseView:

      View接口中定义Activity的UI逻辑。因为有很多方法几乎在每个Activity中都会用到,例如显示和隐藏正在加载进度条,显示Toast提示等,索性将这些方法变成通用的:

public interface BaseView {
    
    void showProgress();
    
    void hideProgress();
    
    void showLoadFailMsg();
    
}
public interface NewsView extends BaseView{

    
    void addNews(List newsList);

    
}

BasePresenter:

       Presenter中可共用的代码就是对View引用的方法了,值得注意的是,上面已经定义好了BaseView,所以我们希望Presenter中持有的View都是BaseView的子类,这里同样需要泛型来约束:

public class BasePresenter {
    /**
     * 绑定的View
     */
    private T mvpView;

    /**
     * 绑定View,一般在初始化中调用该方法
     */
    public void attachView(T mvpView){
        this.mvpView = mvpView;
    }

    /**
     * 断开View,一般在onDestory中调用
     */
     public void detachView(){
         mvpView = null;
     }

    /**
     * 是否与View建立连接
     */
    public boolean isViewAttached(){
        return mvpView!=null;
    }

    /**
     * 获取连接的View
     */
    public T getView(){
        return mvpView;
    }
    
}

BaseActivity:

    BaseActivity主要负责实现BaseView中通用的UI逻辑方法,比如这些通用的方法就不用每个Activity都要去实现一遍了。

public abstract class BaseActivity  extends Activity implements BaseView{
    
    private ProgressDialog mProgressDialog;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mProgressDialog = new ProgressDialog(this);
        mProgressDialog.setCancelable(false);
    }
    
    public void showLoading(){
        if(!mProgressDialog.isShowing()){
            mProgressDialog.show();
        }
    }
    
    public void hideLoading(){
        if(mProgressDialog.isShowing()){
            mProgressDialog.dismiss();
        }
    }
    
    
}

BaseFragment:

public abstract class BaseFragment extends Fragment implements BaseView {

    public abstract int getContentViewId();

    protected abstract void initAllMembersView(Bundle savedInstanceState);

    protected Context mContext;
    protected View mRootView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle
            savedInstanceState) {
        mRootView = inflater.inflate(getContentViewId(), container, false);
        this.mContext = getActivity();
        initAllMembersView(savedInstanceState);
        return mRootView;
    }

    @Override
    public void showLoading() {
        checkActivityAttached();
        ((BaseFragmentActivity) mContext).showLoading();
    }

    @Override
    public void showLoading(String msg) {
        checkActivityAttached();
        ((BaseFragmentActivity) mContext).showLoading(msg);
    }

    @Override
    public void hideLoading() {
        checkActivityAttached();
        ((BaseFragmentActivity) mContext).hideLoading();
    }

    @Override
    public void showToast(String msg) {
        checkActivityAttached();
        ((BaseFragmentActivity) mContext).showToast(msg);
    }

    @Override
    public void showErr() {
        checkActivityAttached();
        ((BaseFragmentActivity) mContext).showErr();
    }

    protected boolean isAttachedContext() {
        return getActivity() != null;
    }

    /**
     * 检查activity连接情况
     */
    public void checkActivityAttached() {
        if (getActivity() == null) {
            throw new ActivityNotAttachedException();
        }
    }

针对Model层的优化:

   Model层相比其他单元来说比较特殊,因为它们更像一个整体,只是单纯的帮上层拿数据而已。再就是MVP的理念是让业务逻辑相互独立,这就导致每个网络请求也被独立成了单个Model,这种方式在实际开发中会出现一些问题:

  1.无法对所有的Model统一管理

  2.每个Model对外提供的获取数据方法不一样,上层请求数据没有规范。

 3.对已存在的Model管理困难,不能直观的统计已经存在的Model

     所以我们更希望Model层是一个庞大且独立的单个模块,请求方式规范化,管理Model更加直观。

MVP模式的优化_第1张图片

如图所示,MVP架构的Model层中,Presenter请求数据不再直接调用具体的Model对象,统一以DataModel类作为请求层的入口,以常量Token区别具体的请求。DataModel会根据Token的不同拉取底层对应的具体Model。

   优化之后的Model层是一个庞大而且独立的模块,对外提供统一的请求方法与请求规则,这样做的好处有很多:

     1.数据请求单独编写,无需配合上层界面测试。

     2.统一管理,修改方便

     3.利用Token类可以直观的统计出已存在的请求接口。

        代码实现:

    根据上节结构图中的描述在考虑到实际请求,我们需要设计一下几个类:

  1.DataModel:数据层顶级入口,项目中所有的数据都由该类流入和流出,负责分发所以的请求数据。

  2.Token:数据请求标识类,定义了项目中所有的数据请求。

  3.BaseMode: 所有的Model的顶级父类,负责对外提供数据请求标准,对内为所有Model提供请求的底层支持。

     最后实现后理想的请求数据方法是:

   BaseModel:

      BaseModel中定义了对外的请求数据规则,包括设置参数的方法和设置Canback的方法,还可以定义一些通用的数据请求方法,比如说网络请求的Get和Post方法。

1.	public abstract class BaseModel  {
2.	
3.	    //数据请求参数
4.	    protected String[] mParams;
5.	
6.	    /**
7.	     * 设置数据请求参数
8.	     * @param args 参数数组
9.	     */
10.	    public  BaseModel params(String... args){
11.	        mParams = args;
12.	        return this;
13.	    }
14.	
15.	    // 添加Callback并执行数据请求
16.	    // 具体的数据请求由子类实现
17.	    public abstract void execute(Callback callback);
18.	
19.	    // 执行Get网络请求,此类看需求由自己选择写与不写
20.	    protected void requestGetAPI(String url,Callback callback){
21.	        //这里写具体的网络请求
22.	    }
23.	
24.	    // 执行Post网络请求,此类看需求由自己选择写与不写
25.	    protected void requestPostAPI(String url, Map params,Callback callback){
26.	        //这里写具体的网络请求
27.	    }
28.	
29.	}

写好BaseModel后再看实现具体Model的方法:

1.	public class UserDataModel extends BaseModel {
2.	
3.	    @Override
4.	    public void execute(final Callback callback) {
5.	
6.	        // 模拟网络请求耗时操作
7.	        new Handler().postDelayed(new Runnable() {
8.	            @Override
9.	            public void run() {
10.	                // mParams 是从父类得到的请求参数
11.	                switch (mParams[0]){
12.	                    case "normal":
13.	                        callback.onSuccess("根据参数"+mParams[0]+"的请求网络数据成功");
14.	                        break;
15.	
16.	                    case "failure":
17.	                        callback.onFailure("请求失败:参数有误");
18.	                        break;
19.	
20.	                    case "error":
21.	                        callback.onError();
22.	                        break;
23.	                }
24.	                callback.onComplete();
25.	            }
26.	        },2000);
27.	    }
28.	}

从上面的代码可以看出,实现具体的Model请求时必须要重写BaseModel的抽象方法execute。

 DataModel:

     由于DataModel负责数据请求的分发,所以我们可以用一个简单工厂模式,然后通过switch(token)语句判断要调用的Model。但是如果这样的话,在实际开发中,我们每次添加一个数据请求的接口,不光需要新建对应的Model和Token,还需要在DataModel类的switch(token)语句中新增加对应的判断,太麻烦。

    于是,我们可以考虑使用反射机制。请求数据时以具体Model的报名+类型作为Token,利用反射机制直接找到对应的Model。

1.	public class DataModel {
2.	
3.	    public static BaseModel request(String token){
4.	
5.	        // 声明一个空的BaseModel
6.	        BaseModel model = null;
7.	
8.	        try {
9.	            //利用反射机制获得对应Model对象的引用
10.	            model = (BaseModel)Class.forName(token).newInstance();
11.	        } catch (ClassNotFoundException e) {
12.	            e.printStackTrace();
13.	        } catch (InstantiationException e) {
14.	            e.printStackTrace();
15.	        } catch (IllegalAccessException e) {
16.	            e.printStackTrace();
17.	        }
18.	        return model;
19.	    }
20.	
21.	}

Token

   由于上节中DataModel使用反射机制获取对应Model的引用,所以Token中存在的就对应的是Model的包名+类名:

1.	public class Token {
2.	
3.	    // 包名
4.	    private static final String PACKAGE_NAME = "com.jesse.mvp.data.model.";
5.	
6.	    // 具体Model
7.	    public static final String API_USER_DATA = PACKAGE_NAME + "UserDataModel";
8.	
9.	}

使用方式:

  完成了Model层之后再去Presenter调用数据时的样子就舒服多了:

1.	DataModel
2.	    // 设置请求标识token
3.	    .request(Token.API_USER_DATA)
4.	    // 添加请求参数,没有则不添加
5.	    .params(userId)
6.	    // 注册监听回调
7.	    .execute(new Callback() {
8.	
9.	           @Override
10.	           public void onSuccess(String data) {
11.	               //调用view接口显示数据
12.	               mView.showData(data);
13.	           }
14.	
15.	           @Override
16.	           public void onFailure(String msg) {
17.	               //调用view接口提示失败信息
18.	               mView.showFailureMessage(msg);
19.	           }
20.	
21.	           @Override
22.	           public void onError() {
23.	               //调用view接口提示请求异常
24.	               mView.showErrorMessage();
25.	           }
26.	
27.	           @Override
28.	           public void onComplete() {
29.	               // 隐藏正在加载进度条
30.	               mView.hideLoading();
31.	           }
32.	 });

添加Model的步骤:

   1.新建一个Model并继承BaseModel,完成具体的数据请求。

   2.在Token中添加对应的Model包名+类名。注意写好注释,方便以后查询。

 总结:

   经过优化的model层很好的统一化了请求方法规范,利用BaseModel不仅有效的减少了请求数据的冗余代码,最关键的还是得到了所有model的集中控制权。例如我们想给所有的请求都加上cookie,直接在BaseModel层做处理即可。

 

 

 

 

 

 

   

 

 

 

 

 

       

 

你可能感兴趣的:(Android)