前面我们分析了MVP与Clean,本文试图以Google构架Demo的Clean分支为样本来分析一下具体的代码实现。由于Clean包含了MVP部分,所以MVP的部分一并说明。
需要强调的是这并不是Clean构架的唯一实现方式,但是其思想可以借鉴。
xxxUseCase
或者xxxInteractor
(在这个Demo中都是UseCase的子类,命名都是以业务相关的动名词的形式,如GetTasks),代表了在Presentation层开发者可以执行的所有Action。由以下几部分组成
Activity: 组合View(Fragemnt)与Presenter,Activity不是View!Activity的OnCreate
中完成3件事情。
请对比学习
Activity的OnCreate中代码如下
// 生成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);
}
// 生成Presenter,注意参数传入了上面生成的View和用到的UseCase
// 注意:在Presenter的构造函数内部会调用View的setPresenter实现双向绑定
mTasksPresenter = new TasksPresenter(
Injection.provideUseCaseHandler(),
tasksFragment,
Injection.provideGetTasks(getApplicationContext()),
Injection.provideCompleteTasks(getApplicationContext()),
Injection.provideActivateTask(getApplicationContext()),
Injection.provideClearCompleteTasks(getApplicationContext())
);
// Presenter状态恢复
if (savedInstanceState != null) {
TasksFilterType currentFiltering =
(TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
mTasksPresenter.setFiltering(currentFiltering);
}
Fragment:代表View,与其他的View作用相同
public class TasksFragment extends Fragment implements TasksContract.View {
public TasksFragment() {
// Requires empty public constructor
}
public static TasksFragment newInstance() {
// 构建Fragment的最佳实践,可以setArgument等
return new TasksFragment();
}
@Override
public void onResume() {
super.onResume();
// Presenter一般都会实现以下通用的方法
mPresenter.start();
}
// 双向绑定时,给Presenter使用的
@Override
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// 一些回调交给Presenter处理
mPresenter.result(requestCode, resultCode);
}
@Nullable
@Override
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使用
@Override
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类
特点如下
void start()
方法一般在View的onResume()
中调用)。public void result(int requestCode, int resultCode)
接口,映射了Fragment(不是Activity)的onActivityResult方法,处理回调。public 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;
@Nullable
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都有启动的那一刻,启动后可能是获取数据(绝大多数),或者其他操作。
@Override
public void start() {
openTask();
}
// 这个很有意思,把Fragment的onActivityResult的值直接传递到Presenter中处理
@Override
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>() {
@Override
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);
}
}
@Override
public void onError() {
// The view may not be able to handle UI updates anymore
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
// 这些暴露的接口都是以用户动作触发为单位的!
@Override
public void editTask() {
// 这里是输入的异常处理,越早越好,不要向下传再抛回来
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mTaskDetailView.showEditTask(mTaskId);
}
@Override
public void deleteTask() {
mUseCaseHandler.execute(mDeleteTask, new DeleteTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<DeleteTask.ResponseValue>() {
@Override
public void onSuccess(DeleteTask.ResponseValue response) {
mTaskDetailView.showTaskDeleted();
}
@Override
public void onError() {
// Show error, log, etc.
}
});
}
// 这些暴露的接口都是以用户动作触发为单位的!
@Override
public void completeTask() {
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mUseCaseHandler.execute(mCompleteTask, new CompleteTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<CompleteTask.ResponseValue>() {
@Override
public void onSuccess(CompleteTask.ResponseValue response) {
mTaskDetailView.showTaskMarkedComplete();
}
@Override
public void onError() {
// Show error, log, etc.
}
});
}
// 这些暴露的接口都是以用户动作触发为单位的!
@Override
public void activateTask() {
if (mTaskId == null || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
mUseCaseHandler.execute(mActivateTask, new ActivateTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<ActivateTask.ResponseValue>() {
@Override
public void onSuccess(ActivateTask.ResponseValue response) {
mTaskDetailView.showTaskMarkedActive();
}
@Override
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保存与恢复数据使用的,不是用户操作
@Override
public void setFiltering(TasksFilterType requestType) {
mCurrentFiltering = requestType;
}
@Override
public TasksFilterType getFiltering() {
return mCurrentFiltering;
}
}
```
void start()
void result(int requestCode, int resultCode);
void setFiltering(TasksFilterType requestType);
TasksFilterType getFiltering();
public interface AddEditTaskContract {
// view层接口,从extends BaseView<Presenter> 就看出来依赖
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
void showTasksList();
void setTitle(String title);
void setDescription(String description);
boolean isActive();
}
// presenter接口
interface Presenter extends BasePresenter {
void saveTask(String title, String description);
void populateTask();
}
}
调用领域层的代码都是在展现层的Presenter类中。
实例:
TaskDetailPresenter
与TasksPresenter
都使用了CompleteTask
命名直观,表示其功能
一个UseCase外而言只执行一个任务,既一个request一个reponse,没有多个方法暴露
Presentation层的调用者使用命令模式执行UseCase
使用命令模式 一个执行器参考UseCaseHandler
,参数是UseCase(命令),Request(输入参数)与Response(输出结果)。UseCaseHandler也是在Activty中构造传入Presenter的。
注意传参的方式,Request与Response都是定义在UseCase中的内部类,用它们来包裹传递的值,不是使用new xxxUseCase(param1,param2).execute(callback)
的样式,或者new xxxUseCase().execute(param1,param2,callback)
实例代码如下
public void clearCompletedTasks() {
mUseCaseHandler.execute(mClearCompleteTasks, new ClearCompleteTasks.RequestValues(),
new UseCase.UseCaseCallback<ClearCompleteTasks.ResponseValue>() {
@Override
public void onSuccess(ClearCompleteTasks.ResponseValue response) {
mTasksView.showCompletedTasksCleared();
loadTasks(false, false);
}
@Override
public void onError() {
mTasksView.showLoadingTasksError();
}
});
}
demo中是这样的,实际开发中有这个需求吗?还是合理划分UseCase就可以了?,尤其是一个UseCase只有执行一个execute,如果一个复杂的UseCase有多个可以复用的任务组成,难道逻辑放到Presenter中?虽然理论上移动端不应该有如此复杂的业务逻辑。展示逻辑(如分页)在Presenter中没有问题。
executeUseCase()
覆写,实现真正的业务逻辑。UseCaseHandler.java
// UseCase泛型参数就是命令模式的几个参数
public class GetTasks extends UseCase<GetTasks.RequestValues, GetTasks.ResponseValue> {
// 注意:无变量缓存
private final TasksRepository mTasksRepository;
private final FilterFactory mFilterFactory;
public GetTasks(@NonNull TasksRepository tasksRepository, @NonNull FilterFactory filterFactory) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
mFilterFactory = checkNotNull(filterFactory, "filterFactory cannot be null!");
}
@Override
protected void executeUseCase(final RequestValues values) {
if (values.isForceUpdate()) {
mTasksRepository.refreshTasks();
}
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
// 纯的业务逻辑,每一次都从数据仓库重新获取过滤
TasksFilterType currentFiltering = values.getCurrentFiltering();
TaskFilter taskFilter = mFilterFactory.create(currentFiltering);
List<Task> tasksFiltered = taskFilter.filter(tasks);
ResponseValue responseValue = new ResponseValue(tasksFiltered);
// 这种通知方式getUseCaseCallback的被封装了
getUseCaseCallback().onSuccess(responseValue);
}
@Override
public void onDataNotAvailable() {
// 这种通知方式getUseCaseCallback的被封装了
getUseCaseCallback().onError();
}
});
}
// 注意这两个类UseCase.RequestValues与UseCase.ResponseValue是空的接口,子类设计也是比较自由的
public static final class RequestValues implements UseCase.RequestValues {
private final TasksFilterType mCurrentFiltering;
private final boolean mForceUpdate;
public RequestValues(boolean forceUpdate, @NonNull TasksFilterType currentFiltering) {
mForceUpdate = forceUpdate;
mCurrentFiltering = checkNotNull(currentFiltering, "currentFiltering cannot be null!");
}
public boolean isForceUpdate() {
return mForceUpdate;
}
public TasksFilterType getCurrentFiltering() {
return mCurrentFiltering;
}
}
public static final class ResponseValue implements UseCase.ResponseValue {
private final List<Task> mTasks;
public ResponseValue(@NonNull List<Task> tasks) {
mTasks = checkNotNull(tasks, "tasks cannot be null!");
}
public List<Task> getTasks() {
return mTasks;
}
}
}
领域层从数据仓库获取接口,
TasksRepository
而非TasksDataSource
接口。虽然持有TasksRepository,不影响测试(本质上它就是个门面,如果测试在注入时替换内部Source就行,参考下面代码),但是很奇怪。我觉得持有TasksDataSource没有问题,可能是TasksRepository语意更清晰。
单例设计,很好理解
有同步方法,也有异步方法。但是没必要用异步的,同步即可。TasksDataSource中有一些异步的Callback接口,README中都说了没有必要。。。
接口中的方法定义与存储的数据相关,如添加一个todo任务,删除一个todo任务,获取所有的todo任务
TasksRepository
暴露的接口只负责获取到数据,而不论数据的来源是哪里(可能是内存,网络,数据库)TasksRepository
的内部设计会引用多个来源TasksDataSource,他们也都实现了TasksRepository
接口。如果需要测试,直接用fake的TasksDataSource替代真实的source即可。// 注意接口设计TasksDataSource与下面mTasksRemoteDataSource等相同
public class TasksRepository implements TasksDataSource {
private static TasksRepository INSTANCE = null;
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
// 缓存
Map<String, Task> mCachedTasks;
// 缓存 数据脏了
boolean mCacheIsDirty = false;
// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
// 这里有些特殊:getInstance的参数是source,RemoteDataSource与LocalDataSource可以替换成fake的source
// 注意:缓存是内置的,没有用外面的
public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
// 数据获取逻辑,可能是从任何地方获取的数据
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
// If the cache is dirty we need to fetch new data from the network.
getTasksFromRemoteDataSource(callback);
} else {
// Query the local storage if available. If not, query the network.
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.saveTask(task);
mTasksLocalDataSource.saveTask(task);
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
}
}
三层的数据Entity与Clean原文中不同
特点:
仅仅讨论Demo的不完善的地方:
优点:
争论: