Android基础

从没有MVX到MVC到MVP - (2 - MVC)

2018-09-19  本文已影响0人  __无语__

简单示例演示如何从自由写法改进到MVC再改进到MVP - 2 - MVC


上一篇: https://www.jianshu.com/p/1c66557fcc05 - 1 - NoMVX,没有任何模式的写法


改成MVC是啥样?

上一篇中页面和点击事件都写在同一个类中了,所以基本没什么层次可言。 要是说那个UserModel是分出去的,也行,毕竟如果把那个也写在Fragment里。。也可以。。只是让那个Fragment更大更乱一些。

那先看看MVC模式的图吧

mvc.png

我并不太愿意去解释这个图,因为这样会让这个文章看起来跟其他的一样 也会很大,我稍微说下自己的看法吧

  1. View -- 他是啥?你可能会说他是Fragment,此时,可以这么理解,但我先说一句,Fragment更像是一个View的容器, 暂且到此为止吧,后面的几篇文章会解释
  2. Controller -- 他是啥?简单说他处理用户在页面交互时的事件比如下拉,点击等
  3. Model -- 这个简单就是UserModel了,里面有picId, name, desc这样的信息
  4. 这里面最重要的是Controller层,他做整体的统筹调度

对着代码看吧,我尽量把代码写的简单易懂。
Model::

public class UserModel {

    private int picId;
    private String userName;
    private String userDesc;

    public UserModel(int picId, String userName, String userDesc) {
        this.picId = picId;
        this.userName = userName;
        this.userDesc = userDesc;
    }

    public int getPicId() {
        return picId;
    }

    public void setPicId(int picId) {
        this.picId = picId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserDesc() {
        return userDesc;
    }

    public void setUserDesc(String userDesc) {
        this.userDesc = userDesc;
    }
}

View::

public class MVCUserListFragment extends Fragment implements IMainPresenter.IFabListener {

    private ScrollChildSwipeRefreshLayout mRefreshLayout;

    private ListView mUserListView;

    private UserListAdapter mUserListAdapter;

    private UserListController mUserListController;

    public MVCUserListFragment() {
        // Required empty public constructor
    }

    public static MVCUserListFragment newInstance() {
        return new MVCUserListFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_list, container, false);
        init(rootView);
        return rootView;
    }

    private void init(View rootView) {
        mRefreshLayout = rootView.findViewById(R.id.refresh_layout);
        mUserListView = rootView.findViewById(R.id.user_list_view);
        mUserListAdapter = new UserListAdapter(null);
        mUserListView.setAdapter(mUserListAdapter);
        mUserListController = new UserListController(this);

        mRefreshLayout.setScrollUpChild(mUserListView);
        mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mUserListController.loadList();
            }
        });

        View emptyView = rootView.findViewById(R.id.empty_list_layout);
        TextView emptyTextView = emptyView.findViewById(R.id.empty_list_text_view);
        String emptyListViewTextString = mUserListController.getEmptyListText();
        String emptyText = getResources().getString(R.string.empty_text, emptyListViewTextString);
        emptyTextView.setText(emptyText);
        mUserListView.setEmptyView(emptyView);
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    public void updateListView(List<UserModel> userModels) {
        mUserListAdapter.setUserModelList(userModels);
        mUserListAdapter.notifyDataSetChanged();
        Toast.makeText(getActivity(), "Update UI from MVC " + userModels.size(), Toast.LENGTH_LONG).show();
    }

    public void showLoading() {
        mRefreshLayout.setRefreshing(true);
    }

    public void hideLoading() {
        mRefreshLayout.setRefreshing(false);
    }

    @Override
    public void onFabClicked() {
        mUserListController.addUser();
    }
}

Controller::

public class UserListController {

    private MVCUserListFragment mMVCUserListFragment;

    private List<UserModel> mUserModels;

    public UserListController(MVCUserListFragment MVCUserListFragment) {
        mMVCUserListFragment = MVCUserListFragment;
    }

    public String getEmptyListText() {
        return "MVC模式,默认没有数据,尝试下拉刷新吧!";
    }

    public void loadList() {
        mMVCUserListFragment.showLoading();
        BackgroundThreadPoster.post(new Runnable() {
            @Override
            public void run() {
                mUserModels = UserManager.callAPIToGetUserList();
                updateUI(mUserModels);
            }
        });
    }

    public void addUser() {
        mMVCUserListFragment.showLoading();
        BackgroundThreadPoster.post(new Runnable() {
            @Override
            public void run() {
                UserModel userModel = UserManager.addUser();
                mUserModels.add(userModel);
                updateUI(mUserModels);
            }
        });
    }



    private void updateUI(final List<UserModel> userModels) {
        MainThreadPoster.post(new Runnable() {
            @Override
            public void run() {
                mMVCUserListFragment.hideLoading();
                mMVCUserListFragment.updateListView(userModels);
            }
        });
    }
}

跟上一篇一样,关注点如下:

  1. View从哪初始化的?
  2. ListView的下拉监听在哪做的?
  3. ListView的下拉回调是在哪里执行的?
  4. ListView下拉回调(模拟)调用API数据加载完毕之后,更新UI是在哪里执行的?
  5. Add User是怎么执行的?
  6. ListView为空时EmptyView是怎么初始化的?

看下改成MVC的逻辑:

  1. View是从Fragment初始化的 - 跟NoMVX一样
  2. ListView的下拉监听也是在Fragment做的 - 跟NoMVX一样
  3. ListView的下拉回调是在哪里执行的 - 这个是在Fragment里面执行的,但他跟NoMVX有本质的区别,那就是这里的回调是调用了Controller的方法,而不是调用Fragment类里面的方法
    private void init(View rootView) {
        mRefreshLayout = rootView.findViewById(R.id.refresh_layout);
        ...
        mRefreshLayout.setScrollUpChild(mUserListView);
        mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                mUserListController.loadList(); // 重要:Fragment (View) 持有Controller的引用
            }
        });
       ...
    }
  1. ListView下拉回调(模拟)调用API数据加载完毕之后,更新UI是在哪里执行的 - 这个是在Controller里面执行的:
    public void loadList() {
        mMVCUserListFragment.showLoading(); // 重要:Controller持有Fragment (View) 的引用
        BackgroundThreadPoster.post(new Runnable() {
            @Override
            public void run() {
                mUserModels = UserManager.callAPIToGetUserList();
                updateUI(mUserModels);
            }
        });
    }
  1. Add User是怎么执行的 - 这个跟第4点类似,Fragment(View)调用Controller, Controller调用API,完成后再调用Fragment (View) 的方法来更新UI -- 这个是MVC的主要执行过程
  2. ListView为空时EmptyView是怎么初始化的 - 在Fragment初始化过程中调用了Controller的方法来得到要显示的内容:
    private void init(View rootView) {
        ...
        TextView emptyTextView = emptyView.findViewById(R.id.empty_list_text_view);
        String emptyListViewTextString = mUserListController.getEmptyListText(); // 此处调用了Controller的方法
        String emptyText = getResources().getString(R.string.empty_text, emptyListViewTextString);
        emptyTextView.setText(emptyText);
        mUserListView.setEmptyView(emptyView);
    }

我们来分析下把 Fragment 拆成 Fragment + Controller 有什么好处, 难道我们就是为了耍下花样吗?

  1. Fragment现在类变小了。他基本上就是从layout里面加载一个布局,然后加上监听,不过监听回调都委派给了Controller,他也不用关心什么时候要更新UI了,这些都放到Controller中来处理了
  2. Controller他的职责也很明确,那就是处理Fragment拆分出来的事情,主要可以概括为两点:
  1. 调用API或数据库执行数据交互,并在完毕之后更新UI
  2. Fragment中有一些不确定的因素,比如上例中,TextView显示的文字,可能也是需要根据运行时情况不同而返回不同的文字(比如,返回当前登录的用户名,当前页面名称等等),这样一来,Fragment是不需要关心这些与数据相关的细节的,都由Controller来负责了

你可能还会再问,现在Fragment是小了可他把所有的工作都交给Controller去做了,这就好像是把AB分成了A+B,有什么好处,你告诉我。

我得说如果你这么想了 那这是一个好问题
这么说吧,如果需求改变了

可以看出来MVC的职责更纯粹一些,Fragment在MVC模式中只负责View, Controller只负责数据交互然后更新UI。
看起来不错对吧?可为什么还要提到MVP呢?一定是MVC还有什么不足之处。
是的。
这里我只提一点,那就是Controller引用了Fragment的实例,而这个对单元测试是比较困难的,因为在Android环境之外的单元测试环境中难以模拟Fragment这个类。

(其他MVC的不足还请自行查看其他长篇的文字。。)

那MVP比MVC又好到哪了呢?

具体代码详见:
https://github.com/chinalwb/mvc_mvp_mvvm

上一篇下一篇

猜你喜欢

热点阅读