前面我们分析了MVP与Clean,本文试图以Google构架Demo的Clean分支为样本来分析一下具体的代码实现。由于Clean包含了MVP部分,所以MVP的部分一并说明。
需要强调的是这并不是Clean构架的唯一实现方式,但是其思想可以借鉴。

总体结构

Diagram 分为三部分:

展现(Presentation)层--MVP

由以下几部分组成

  1. 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);
        }
    
  2. 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 onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mListAdapter = new TasksAdapter(new ArrayList(0), mItemListener);
}

@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来管理数据恢复这些事情,职责清晰。

  1. Presenter类
    特点如下

    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;
}
}

```

  1. Contract--接口定义
    这个类是demo的特色,把一个业务的展现层与领域层之间的接口归类到一个类中十分清晰
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();
    }
}

领域(Domain)层--UseCase

调用领域层的代码都是在展现层的Presenter类中。

UseCase的外部特点:

实例:TaskDetailPresenterTasksPresenter 都使用了CompleteTask

实例代码如下

	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();
                    }
                });
    }

UseCase的内部实现

demo中是这样的,实际开发中有这个需求吗?还是合理划分UseCase就可以了?,尤其是一个UseCase只有执行一个execute,如果一个复杂的UseCase有多个可以复用的任务组成,难道逻辑放到Presenter中?虽然理论上移动端不应该有如此复杂的业务逻辑。展示逻辑(如分页)在Presenter中没有问题。

// 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;
        }
    }
}

数据(Data)层--Repository模式

领域层从数据仓库获取接口,

Repository的外部特点

虽然持有TasksRepository,不影响测试(本质上它就是个门面,如果测试在注入时替换内部Source就行,参考下面代码),但是很奇怪。我觉得持有TasksDataSource没有问题,可能是TasksRepository语意更清晰。

Repository的内部实现

// 注意接口设计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的不完善的地方:

优点:

争论: