前面我们分析了MVP与Clean,本文试图以Google构架Demo的Clean分支为样本来分析一下具体的代码实现。由于Clean包含了MVP部分,所以MVP的部分一并说明。
需要强调的是这并不是Clean构架的唯一实现方式,但是其思想可以借鉴。
总体结构
分为三部分:
- 展现(Presentation)层: 核心是MVP ,做UI控制。
- 领域(Domain)层: 核心是UseCase 这一层是所有的业务逻辑,这一层的类都叫做
xxxUseCase
或者xxxInteractor
(在这个Demo中都是UseCase的子类,命名都是以业务相关的动名词的形式,如GetTasks),代表了在Presentation层开发者可以执行的所有Action。 - 数据(Data)层: 核心是Repository,是使用数据仓库模式。
展现(Presentation)层—MVP
由以下几部分组成
Activity: 组合View(Fragemnt)与Presenter,Activity不是View!Activity的
OnCreate
中完成3件事情。- 构建View,这里都是Fragment。
生成所有Presenter用到的的UseCase,UseCase用的UseCaseHandler,Repository:目的是方便修改注入,用Provider的方式代替注入框架,全部在Activity中注入完成,如果使用Dagger等注入框架,这里不必要。
请对比学习
生成Presenter并双向绑定:注意参数:注入刚才的View,和用到的所有UserCase.
Presenter的状态恢复,在Activity重建时,都是重新构建Presenter,并且只恢复Presenter中某些数据的状态。(这一步可选,只恢复使用的数据,大部分情况下并没有恢复数据,重新构建Presenter。这里的实现简单粗暴,也可以用Fragment来保持Presenter,关于Presenter的恢复问题MVP一节中有讨论)
Activity的OnCreate中代码如下
1
2
3
4
5
6
7
8
9// 生成View
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}1
2
3
4
5
6
7
8
9
10// 生成Presenter,注意参数传入了上面生成的View和用到的UseCase
// 注意:在Presenter的构造函数内部会调用View的setPresenter实现双向绑定
mTasksPresenter = new TasksPresenter(
Injection.provideUseCaseHandler(),
tasksFragment,
Injection.provideGetTasks(getApplicationContext()),
Injection.provideCompleteTasks(getApplicationContext()),
Injection.provideActivateTask(getApplicationContext()),
Injection.provideClearCompleteTasks(getApplicationContext())
);1
2
3
4
5
6// Presenter状态恢复
if (savedInstanceState != null) {
TasksFilterType currentFiltering =
(TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
mTasksPresenter.setFiltering(currentFiltering);
}
Fragment:代表View,与其他的View作用相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public class TasksFragment extends Fragment implements TasksContract.View {
public TasksFragment() {
// Requires empty public constructor
}
public static TasksFragment newInstance() {
// 构建Fragment的最佳实践,可以setArgument等
return new TasksFragment();
}
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListAdapter = new TasksAdapter(new ArrayList<Task>(0), mItemListener);
}
public void onResume() {
super.onResume();
// Presenter一般都会实现以下通用的方法
mPresenter.start();
}
// 双向绑定时,给Presenter使用的
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// 一些回调交给Presenter处理
mPresenter.result(requestCode, resultCode);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false);
// 这个看情况,界面中有无需要保持的数据(如一些用户输入的信息)。
// 由于这里没有使用Fragemnt来保持Presenter,这个也可以不加
// setRetainInstance(true);
return root;
}
// 其他的View接口的方法实现,给Presenter使用
public void showTasksList() {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
}从上述代码中,我们可以得到几点信息:
- 在View的生命周期中调用对应的Presenter方法。
- View与Presenter的绑定时机:这里的View(Fragment)比较被动,通过在Presenter的构造函数中调用View接口的setPresnter方法注入Presenter,实现双向绑定。
Fragment没有履行Presenter保持的职责,他只负责保持界面的数据(如果有必要,参考
AddEditTaskFragment.java
)。之所以这样,一部分原因是由Activity来管理数据恢复这些事情,职责清晰。
Presenter类
特点如下- 实现了xxxContract.Presenter接口,包括该接口的父接口BasePresenter中定义的生命周期映射(只有
void start()
方法一般在View的onResume()
中调用)。 - 暴露了的接口要明确。大部分暴露的接口都是View使用的操作(由用户行为触发)与Activity用到的功能(数据保持恢复型操作)。如何定义,定义什么接口具体查看Contract
- 构造函数中与Fragment绑定,setPresenter
- 一个Presenter中含有多个UseCase
- 一个对外接口可以单独运行一个UseCase或者组合运行多个UseCase,嵌套调用。
- 可能有
public void result(int requestCode, int resultCode)
接口,映射了Fragment(不是Activity)的onActivityResult方法,处理回调。 - 额外的还有数据获取与恢复接口给Activity调用
接口中对View传来的原始数据进行处理。如判空等,在Presenter中,如果是null,直接调用View告知用户。而不是把这些值向下传入Domain层。原则:异常输入越早处理约好
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181public class TaskDetailPresenter implements TaskDetailContract.Presenter {
private final TaskDetailContract.View mTaskDetailView;
private final UseCaseHandler mUseCaseHandler;
// 含有多个UseCase
private final GetTask mGetTask;
private final CompleteTask mCompleteTask;
private final ActivateTask mActivateTask;
private final DeleteTask mDeleteTask;
private String mTaskId;
public TaskDetailPresenter(@NonNull UseCaseHandler useCaseHandler,
@Nullable String taskId,
@NonNull TaskDetailContract.View taskDetailView,
@NonNull GetTask getTask,
@NonNull CompleteTask completeTask,
@NonNull ActivateTask activateTask,
@NonNull DeleteTask deleteTask) {
mTaskId = taskId;
// 这些判空也是尽早发现问题的思想
mUseCaseHandler = checkNotNull(useCaseHandler, "useCaseHandler cannot be null!");
mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
mGetTask = checkNotNull(getTask, "getTask cannot be null!");
mCompleteTask = checkNotNull(completeTask, "completeTask cannot be null!");
mActivateTask = checkNotNull(activateTask, "activateTask cannot be null!");
mDeleteTask = checkNotNull(deleteTask, "deleteTask cannot be null!");
mTaskDetailView.setPresenter(this);
}
// 抽象了一下,几乎所有的Presenter都有启动的那一刻,启动后可能是获取数据(绝大多数),或者其他操作。
public void start() {
openTask();
}
// 这个很有意思,把Fragment的onActivityResult的值直接传递到Presenter中处理
public void result(int requestCode, int resultCode) {
// If a task was successfully added, show snackbar
if (AddEditTaskActivity.REQUEST_ADD_TASK == requestCode
&& Activity.RESULT_OK == resultCode) {
mTasksView.showSuccessfullySavedMessage();
}
}
private void openTask() {
// 这里是输入的异常处理,越早越好,不要向下传再抛回来
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mTaskDetailView.setLoadingIndicator(true);
mUseCaseHandler.execute(mGetTask, new GetTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<GetTask.ResponseValue>() {
public void onSuccess(GetTask.ResponseValue response) {
Task task = response.getTask();
// The view may not be able to handle UI updates anymore
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
mTaskDetailView.showMissingTask();
} else {
showTask(task);
}
}
public void onError() {
// The view may not be able to handle UI updates anymore
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
// 这些暴露的接口都是以用户动作触发为单位的!
public void editTask() {
// 这里是输入的异常处理,越早越好,不要向下传再抛回来
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mTaskDetailView.showEditTask(mTaskId);
}
public void deleteTask() {
mUseCaseHandler.execute(mDeleteTask, new DeleteTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<DeleteTask.ResponseValue>() {
public void onSuccess(DeleteTask.ResponseValue response) {
mTaskDetailView.showTaskDeleted();
}
public void onError() {
// Show error, log, etc.
}
});
}
// 这些暴露的接口都是以用户动作触发为单位的!
public void completeTask() {
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mUseCaseHandler.execute(mCompleteTask, new CompleteTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<CompleteTask.ResponseValue>() {
public void onSuccess(CompleteTask.ResponseValue response) {
mTaskDetailView.showTaskMarkedComplete();
}
public void onError() {
// Show error, log, etc.
}
});
}
// 这些暴露的接口都是以用户动作触发为单位的!
public void activateTask() {
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mUseCaseHandler.execute(mActivateTask, new ActivateTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<ActivateTask.ResponseValue>() {
public void onSuccess(ActivateTask.ResponseValue response) {
mTaskDetailView.showTaskMarkedActive();
}
public void onError() {
// Show error, log, etc.
}
});
}
private void showTask(Task task) {
String title = task.getTitle();
String description = task.getDescription();
if (title != null && title.isEmpty()) {
mTaskDetailView.hideTitle();
} else {
mTaskDetailView.showTitle(title);
}
if (description != null && description.isEmpty()) {
mTaskDetailView.hideDescription();
} else {
mTaskDetailView.showDescription(description);
}
mTaskDetailView.showCompletionStatus(task.isCompleted());
}
// 这两个方法比较特别,是Avtivity保存与恢复数据使用的,不是用户操作
public void setFiltering(TasksFilterType requestType) {
mCurrentFiltering = requestType;
}
public TasksFilterType getFiltering() {
return mCurrentFiltering;
}
}
- 实现了xxxContract.Presenter接口,包括该接口的父接口BasePresenter中定义的生命周期映射(只有
Contract—接口定义
这个类是demo的特色,把一个业务的展现层与领域层之间的接口归类到一个类中十分清晰- View层的操作(往往由用户触发)
- 编辑
- 添加
- 删除
- 点击
- 下拉。。。
- View的生命周期映射、抽象
- onResume —
void start()
- onPause
- onDestroy。。。
void result(int requestCode, int resultCode);
- onResume —
- 数据存储恢复(这个demo是Activity使用)
- onSaveInstance —
void setFiltering(TasksFilterType requestType);
- onRestoreInstance —
TasksFilterType getFiltering();
- onSaveInstance —
- View层的操作(往往由用户触发)
1 | public interface AddEditTaskContract { |
领域(Domain)层—UseCase
调用领域层的代码都是在展现层的Presenter类中。
UseCase的外部特点:
- 独立性,可复用,一个业务定义的UseCase可以被其他业务单独使用
实例:
TaskDetailPresenter
与TasksPresenter
都使用了CompleteTask
- 命名直观,表示其功能
- 一个UseCase外而言只执行一个任务,既一个request一个reponse,没有多个方法暴露
- Presentation层的调用者使用命令模式执行UseCase
- 单独运行一个UseCase
- 组合运行多个UseCase:嵌套调用
- 使用命令模式 一个执行器参考
UseCaseHandler
,参数是UseCase(命令),Request(输入参数)与Response(输出结果)。UseCaseHandler也是在Activty中构造传入Presenter的。 - 注意传参的方式,Request与Response都是定义在UseCase中的内部类,用它们来包裹传递的值,不是使用
new xxxUseCase(param1,param2).execute(callback)
的样式,或者new xxxUseCase().execute(param1,param2,callback)
实例代码如下
1 | public void clearCompletedTasks() { |
UseCase的内部实现
- UseCase内部没有调用其他UseCase,组合由Presenter完成,UseCase之间不可以互相调用???
demo中是这样的,实际开发中有这个需求吗?还是合理划分UseCase就可以了?,尤其是一个UseCase只有执行一个execute,如果一个复杂的UseCase有多个可以复用的任务组成,难道逻辑放到Presenter中?虽然理论上移动端不应该有如此复杂的业务逻辑。展示逻辑(如分页)在Presenter中没有问题。
- UseCase内部的
executeUseCase()
覆写,实现真正的业务逻辑。 - 内部类定义Request与Reponse,包裹传递的实体。
- 没有在UseCase内的变量缓存数据
- 执行器executeUseCase默认在在非UI线程执行UseCase,但是CallBack会回到UI线程,参考
UseCaseHandler.java
1 | // UseCase泛型参数就是命令模式的几个参数 |
数据(Data)层—Repository模式
领域层从数据仓库获取接口,
Repository的外部特点
- 领域层直接持有数据层的类
TasksRepository
而非TasksDataSource
接口。
虽然持有TasksRepository,不影响测试(本质上它就是个门面,如果测试在注入时替换内部Source就行,参考下面代码),但是很奇怪。我觉得持有TasksDataSource没有问题,可能是TasksRepository语意更清晰。
- 单例设计,很好理解
有同步方法,也有异步方法。但是没必要用异步的,同步即可。TasksDataSource中有一些异步的Callback接口,README中都说了没有必要。。。
接口中的方法定义与存储的数据相关,如添加一个todo任务,删除一个todo任务,获取所有的todo任务
Repository的内部实现
- 内部有缓存,单仅仅是原始数据缓存,使用HashMap实现,比较简单。
- Repository模式类似与装饰模式,
TasksRepository
暴露的接口只负责获取到数据,而不论数据的来源是哪里(可能是内存,网络,数据库) TasksRepository
的内部设计会引用多个来源TasksDataSource,他们也都实现了TasksRepository
接口。如果需要测试,直接用fake的TasksDataSource替代真实的source即可。
1 | // 注意接口设计TasksDataSource与下面mTasksRemoteDataSource等相同 |
数据实体
三层的数据Entity与Clean原文中不同
特点:
- 公用,三层通用了一个数据Model—Task。减少了Clean构架的三层数据模型之间的转换
总结
仅仅讨论Demo的不完善的地方:
- 没有考虑P层的Presenter的保持
- Domain层没有负责的业务逻辑,没有多UseCase相互调用的例子
- Domain数据处理简单没有性能问题。没有缓存
- 没有Notify机制的示例。都是一个request一个reponse的简单请求。
优点:
- Activty与Fragment职责明确
- Contract设计
- 轻客户端思想,Domain尽量简单(与上面对应,哈哈)
- UseCase的简单设计思想,使得UseCese可以在其他模块复用(参考GetTasks用例)
- Domain的命令模式,设计可以参考
- 数据仓库的设计(缓存和多Source思想)
争论:
- StatisticsPresenter中的统计逻辑位置是否有问题?在主线程?为什么不用一个UseCase?