MVX调研有话说

小序

随着开发框架的潮流影响,再加上组织上给予的压力。看来不得不迈出MVC君的守望之门了,仿佛一股热流向此处涌来,其实际上已然有好多前辈写下了大写的文章在等着我。此刻起只要好好了解一番,再弄一两个小Demo便可造就一片新的天地了,然后就可以在蓝天白云下沐浴在阳光下奔跑了,光想想还是有点小激动的。


MVC

1.1.介绍

Created with Raphaël 2.1.0ViewViewControllerControllerModelModelUser尝试点击触发点击事件发起数据请求处理数据完毕,反馈更新User看到效果

1.2.还是介绍

以上介绍主要是绘制并介绍了MVC层与层之间的交互过程,下面具体整理下各个层的含义与作用域:

who what where
Model 业务逻辑处理层 数据源处理,网络加载,复杂算法
View 界面显示层 布局文件.xml
Controller 控制器 Activity、Fragment

1.3.潜在输出

  • 将内容显示以及业务逻辑处理分隔开来,模块职责划分明确,有利于代码维护;
  • 提升项目的可扩展性与维护性,并降低耦合度;
  • 对业务逻辑复杂且页面较多的项目更能提升MVC的优势;

1.4.承受伤害

  • View与Controller融合在一起。大多数人通过Activity/Fragment直接控制View内容的更新;
  • View完全依赖Model。忽略Controller的控制直接从Model获取数据;
  • View自己承担部分业务逻辑。Model失去可重用的业务逻辑处理;
  • Controller同时负责View与Model的任务处理。使得代码异常臃肿,显得较为混乱;

1.5.代码飘过

/**
 8. Controller
 */
public class MainActivity extends AppCompatActivity {

    // 文本
    private TextView mMainTv;
    // 按钮
    private Button mMainBtn;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    /**
     * View
     */
    private void initView() {
        this.mMainTv = (TextView) findViewById(R.id.main_tv);
        this.mMainBtn = (Button) findViewById(R.id.main_btn);

        mMainBtn.setOnClickListener(v -> {
                mMainTv.setText(getDateTimeStr());
        });
    }

    /**
     * Model
     *
     * @return 当前日期时间
     */
    private String getDateTimeStr() {
        Date curDate = new Date(System.currentTimeMillis());
        return SimpleDateFormat.getDateTimeInstance().format(curDate);
    }
}

以上代码只是简单实现按钮点击获取当前的时间,并赋值给文本显示。其主要是表现大多数停留在MVC的伙伴,都会以这样一个方式去创建模块的页面和功能。至少接触过的很多项目里是这样的,当然并没有反对的意思,因为只要合理分配也是极好的。这里更多的是为了鉴于这个类来比较:

  1. 清晰mvc再战android

MVP

2.1.介绍

Created with Raphaël 2.1.0ViewViewPresenterPresenterModelModelUser尝试点击触发点击事件发起数据请求处理数据完毕反馈更新User看到效果

2.2.依然是介绍

同理,以上介绍主要是绘制并介绍了MVP层与层之间的交互过程。其情节内容没有改动,因为一个功能的思想一样不受使用模式的影响。下面具体整理下各个层的含义与作用域:

who what where
Model 数据处理层 数据的检索,存储等操作
View 界面显示层 Activity、Fragment、布局文件.xml
Presenter 层现器 作为View与Model两者的中间枢纽,处理与用户交互的负责逻辑

2.3.潜在输出

  • View层与Model层完全分离,两者间的修改互不影响;
  • Presenter控制View与Model之间的交互,大大提升模型的使用效率;
  • Presenter可以被多个View绑定(MVC当中Controller服务多个View);
  • 可完全脱离用户接口实现单元测试;
  • 解决MVC当中Activity代码臃肿的问题;

2.4.承受伤害

  • 由于Presenter可以被多个View绑定,且如果与Presenter之间的交互现象过于频繁,一旦View需要变更,那么Presenter也需要变更从而影响Presenter的复用性;
  • 大量的View与Model的手动同步逻辑,造成Presenter比较笨重,维护起来会比较困难;

2.5.前期装备

啥?还有前期装备,这是要逆天嘛,还有这装备到底是什么。请恕我直言,如果前期没有辅助那必须吃力些呀,所以View有理由学会摆脱与Presenter的直接交互,继而通过View interface来配合View接受Presenter返回结果的处理。下面是前期装备属性:

  • 降低View与Presenter之间耦合度;
  • 方便进行单元测试,可绕过View直接定义类实现interface模拟View与Presenter之间的交互;

2.6.后期装备

啥?还有后期,这发育的也太狠了吧。请恕我直言,这装备我也还没用过。当然它的属性是很诱人的,满满的被动输出伤害有木有。其实际上就是前辈们挖掘出来的两种View模式,用于划分View与Presenter的任务内容,具体如下:

who what
PV(Passive View) View的UI元素委托给Presenter操作
SoC(Supervising Controller) View自己负责UI处理以及数据绑定

2.7.代码飘过

相信刚接触MVP模式也许会像我一样,摸不清构建顺序吧。因为项目比较紧急,且又急于尝试MVP的实现,我仅在项目里使用了MVP+XUtils3的框架。下面贴出的代码仅供参考。

  • 基础构建
public class BasePresenter<V> {

    public V mvpView;
    public Context mContext;

    public BasePresenter(Context context, V mvpView) {
        this.mContext = context;
        this.mvpView = mvpView;
    }

    public void detachView() {
        this.mvpView = null;
    }
}
public abstract class BaseActivity<P extends BasePresenter> extends AppCompatActivity {

    public P mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mvpPresenter = createPresenter();
    }

    protected abstract P createPresenter();
}
public abstract class BaseFragment<P extends BasePresenter> extends Fragement {

    public P mvpPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mvpPresenter = createPresenter();
    }

    protected abstract P createPresenter();
}
  • 具体实现,这里以请求登录操作为例。View interfacePresenterActivity/Fragment(感觉顺些)
public interface LoginView {

    void userLoginSuccess(UserInfo userInfo);

    void userLoginFail(String failMsg);
}
public class LoginPresenter extends BasePresenter<LoginView> {

    public void loginUser(String account, String pwd) {
        UserLogin userLogin = new UserLogin();
        userLogin.Account = account;
        userLogin.Password = pwd;

        RequestParams params = new RequestParams(ApiConfig.USER_IP + ApiConfig.USER_LOGIN);
        params.setBodyContent(userLogin.toJSONString());

        // 请求结果需对mvpView判空处理
        x.http().post(params, new Callback.CommonCallback<UserInfo>() {

            @Override
            public void onSuccess(UserInfo model) {
                if (null == mvpView) return;
                if (model.getStatusCode() == 200) {
                    mvpView.userLoginSuccess(model.getData());
                } else {
                    mvpView.userLoginFail(model.getMessage());
                }
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                if (null == mvpView) return;
                mvpView.userLoginFail("请求出错/网络异常");
            }

            @Override
            public void onCancelled(CancelledException cex) {}

            @Override
            public void onFinished() {}
        });
    }
}
@ContentView(R.layout.activity_login)
public class LoginActivity extends BaseActivity<LoginPresenter> implements LoginView {

    @ViewInject(R.id.login_edt_id)
    private EditText mIdEdt;

    @ViewInject(R.id.login_edt_pwd)
    private EditText mPwdEdt;

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter(this, this);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        x.view().inject(this);
    }

    @Event(value = {R.id.login_btn}, type = View.OnClickListener.class)
    private void onAppClick(View view) {
        String idStr = mIdEdt.getText().toString();
        String pwdStr = mPwdEdt.getText().toString();
        // 为了代码简洁些,判空等处理被舍弃了
        mvpPresenter.loginUser(idStr, pwdStr);
    }

    @Override
    public void userLoginSuccess(UserInfo userInfo) {
        // 登录成功,执行相应操作
    }

    @Override
    public void userLoginFail(String failMsg) {
        // 登录失败,Toast提示错误信息
    }
}

以上只是个人实践内容,其实际上还有很多与MVP结合使用的大写之作,推荐:

  1. MVP+Retrofit+RxJava
  2. MVP+Dagger2+RxJava+Retrofit2

MVVM

3.1.介绍

Created with Raphaël 2.1.0ViewViewViewModelViewModelModelModelUser尝试点击数据源绑定更新,触发点击事件发起数据请求,更新数据源User看到效果

3.2.当然还是介绍

以上绘制并介绍了MVVM层与层之间的交互过程,其左边描述View与数据源的绑定,即控件显示数据与绑定的对象数据同步更新(可以忽略时序图的前后顺序这一说,只怪提供的绘图方法有限)。下面具体整理下各个层的含义与作用域:

who what where
Model 数据处理层 数据的检索,存储等操作
View 界面显示层 Activity、Fragment、布局文件.xml
ViewModel 视图模型 可理解为View的Model和Presenter之间的融合

3.3.潜在输出

  • 数据捆绑UI更新。数据与业务逻辑处在一个独立的ViewModel当中,由数据主导UI的更新(eg. DataBinding框架实现被捆绑的UI更新)
  • 低耦合度。ViewModel不对View的任何控件保持持有状态,更改UI无需修改ViewModel的实现;
  • 团队协作。扮演UI处理以及数据和业务逻辑的两个不同角色;
  • 复用性强。同一个ViewModel可以为多个View服务;
  • 单元测试。只需要在ViewModel执行单元测试,完全不需要依赖View的任何操作;

3.4.承受伤害

  • 多个View与ViewModel执行捆绑,造成ViewModel比较笨重;
  • 数据对象的直接引用使得后期维护起来会比较困难;

3.5.这里木有代码

只是了解了下但还没有确切实践过MVVM,暂时也就没有代码飘过了


总结

4.1.共同点

who what
View 一直扮演者与用户交互的角色
Model 因数据处理而忙碌着

4.2.不同点

who what
Controller 被动的负责将View的需求告知Model处理并让Model通知View更新
Presenter 在Model前面拿下View的请求做一定的业务处理才给回Model做数据操作处理,
数据请求处理结束后再将Model数据返回给View并提示更新
ViewModel 在Presenter的基础上绑定并实现了对View的部分数据操作


PS1.没有什么绝对好的开发模式,倘若没有规范好自身的代码,一切都将是空谈
PS2.以上内容若存在不足之处,还望大虾们指点一二

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章