简单示例演示如何从自由写法改进到MVC再改进到MVP - 2 - MVC
上一篇: https://www.jianshu.com/p/1c66557fcc05 - 1 - NoMVX,没有任何模式的写法
改成MVC是啥样?
上一篇中页面和点击事件都写在同一个类中了,所以基本没什么层次可言。 要是说那个UserModel是分出去的,也行,毕竟如果把那个也写在Fragment里。。也可以。。只是让那个Fragment更大更乱一些。
那先看看MVC模式的图吧
mvc.png我并不太愿意去解释这个图,因为这样会让这个文章看起来跟其他的一样 也会很大,我稍微说下自己的看法吧
- View -- 他是啥?你可能会说他是Fragment,此时,可以这么理解,但我先说一句,Fragment更像是一个View的容器, 暂且到此为止吧,后面的几篇文章会解释
- Controller -- 他是啥?简单说他处理用户在页面交互时的事件比如下拉,点击等
- Model -- 这个简单就是UserModel了,里面有picId, name, desc这样的信息
- 这里面最重要的是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);
}
});
}
}
跟上一篇一样,关注点如下:
- View从哪初始化的?
- ListView的下拉监听在哪做的?
- ListView的下拉回调是在哪里执行的?
- ListView下拉回调(模拟)调用API数据加载完毕之后,更新UI是在哪里执行的?
- Add User是怎么执行的?
- ListView为空时EmptyView是怎么初始化的?
看下改成MVC的逻辑:
- View是从Fragment初始化的 - 跟NoMVX一样
- ListView的下拉监听也是在Fragment做的 - 跟NoMVX一样
- 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的引用
}
});
...
}
- 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);
}
});
}
- Add User是怎么执行的 - 这个跟第4点类似,Fragment(View)调用Controller, Controller调用API,完成后再调用Fragment (View) 的方法来更新UI -- 这个是MVC的主要执行过程
- 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 有什么好处, 难道我们就是为了耍下花样吗?
- Fragment现在类变小了。他基本上就是从layout里面加载一个布局,然后加上监听,不过监听回调都委派给了Controller,他也不用关心什么时候要更新UI了,这些都放到Controller中来处理了
- Controller他的职责也很明确,那就是处理Fragment拆分出来的事情,主要可以概括为两点:
- 调用API或数据库执行数据交互,并在完毕之后更新UI
- Fragment中有一些不确定的因素,比如上例中,TextView显示的文字,可能也是需要根据运行时情况不同而返回不同的文字(比如,返回当前登录的用户名,当前页面名称等等),这样一来,Fragment是不需要关心这些与数据相关的细节的,都由Controller来负责了
你可能还会再问,现在Fragment是小了可他把所有的工作都交给Controller去做了,这就好像是把AB分成了A+B,有什么好处,你告诉我。
我得说如果你这么想了 那这是一个好问题
这么说吧,如果需求改变了
- 比如改变了UI,对于之前NoMVX来说, 是需要修改Fragment类的,而这个有可能会影响到数据访问方面的功能,当然你可以很小心的去做来保证他不受影响,但这个可能性还是存在且有点大的。如果对于MVC来说,你改Fragment不影响Controller,只要数据访问那边的需求不变,Controller几乎是不用动的
- 再比如改变了数据来源,原来是从数据库中取,现在改变成从服务器取,那NoMVX也是需要改Fragment的,而这个可能影响UI部分。对于MVC来说,只需要在Controller里面改就行了,而这个不影响Fragment的UI显示。
可以看出来MVC的职责更纯粹一些,Fragment在MVC模式中只负责View, Controller只负责数据交互然后更新UI。
看起来不错对吧?可为什么还要提到MVP呢?一定是MVC还有什么不足之处。
是的。
这里我只提一点,那就是Controller引用了Fragment的实例,而这个对单元测试是比较困难的,因为在Android环境之外的单元测试环境中难以模拟Fragment这个类。
(其他MVC的不足还请自行查看其他长篇的文字。。)
那MVP比MVC又好到哪了呢?
网友评论