在架构简介中,最重要的是Clean Architecture的思想。
Clean Architecture
在原版的介绍中,有一张很不错的图:
乍一看这张图,就像洋葱,一层包一层,其核心理念就是向内依赖。也就是说:外层依赖内层,内层不能依赖外层,层层向内依赖。越往内,越稳定。在这个核心理念下,定义了每一层的名称和职责,见图,不复述。
示例代码在代码结构上又分为三个层次:
相比mvp,最大的变化就是增加了Domain Layer以及Use Case。Use Case定义了对Entity的使用范例。其中包含了业务逻辑,这在mvp架构中是应该在Presenter中的,现在剥离出来了。好处是结构更清晰,代码复用更高了。
光这么描述,没啥感觉,还是来看看代码。
代码分析
首先拉取分支:
git checkout -b todo-mvp-clean origin/todo-mvp-clean
我们只分析项目中的添加task的业务逻辑即可,其他业务逻辑类似。
先看看代码结构:
同样的,最核心的实体是model: Task。围绕Task有若干包含业务逻辑的usecase:DeleteTask、GetTask、SaveTask。 model和usecase共同构建了domain Layer(当然model可能只有一个,但是针对特定业务可能有不同的usecase,所以不一定要usecase和model在每个业务中都同时存在)。
我们看一个具体的usecase:
public class GetTask extends UseCase<GetTask.RequestValues, GetTask.ResponseValue> {
private final TasksRepository mTasksRepository;
public GetTask(@NonNull TasksRepository tasksRepository) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
}
@Override
protected void executeUseCase(final RequestValues values) {
mTasksRepository.getTask(values.getTaskId(), new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
if (task != null) {
ResponseValue responseValue = new ResponseValue(task);
getUseCaseCallback().onSuccess(responseValue);
} else {
getUseCaseCallback().onError();
}
}
@Override
public void onDataNotAvailable() {
getUseCaseCallback().onError();
}
});
}
public static final class RequestValues implements UseCase.RequestValues {
private final String mTaskId;
public RequestValues(@NonNull String taskId) {
mTaskId = checkNotNull(taskId, "taskId cannot be null!");
}
public String getTaskId() {
return mTaskId;
}
}
public static final class ResponseValue implements UseCase.ResponseValue {
private Task mTask;
public ResponseValue(@NonNull Task task) {
mTask = checkNotNull(task, "task cannot be null!");
}
public Task getTask() {
return mTask;
}
}
}
可以看到,其中使用Data Layer层的接口获取数据。
再来看看Presenter:
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter {
private final AddEditTaskContract.View mAddTaskView;
private final GetTask mGetTask;
private final SaveTask mSaveTask;
private final UseCaseHandler mUseCaseHandler;
@Nullable
private String mTaskId;
private boolean mIsDataMissing;
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param addTaskView the add/edit view
* @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
*/
public AddEditTaskPresenter(@NonNull UseCaseHandler useCaseHandler, @Nullable String taskId,
@NonNull AddEditTaskContract.View addTaskView, @NonNull GetTask getTask,
@NonNull SaveTask saveTask, boolean shouldLoadDataFromRepo) {
mUseCaseHandler = checkNotNull(useCaseHandler, "useCaseHandler cannot be null!");
mTaskId = taskId;
mAddTaskView = checkNotNull(addTaskView, "addTaskView cannot be null!");
mGetTask = checkNotNull(getTask, "getTask cannot be null!");
mSaveTask = checkNotNull(saveTask, "saveTask cannot be null!");
mIsDataMissing = shouldLoadDataFromRepo;
mAddTaskView.setPresenter(this);
}
@Override
public void start() {
if (!isNewTask() && mIsDataMissing) {
populateTask();
}
}
@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}
@Override
public void populateTask() {
if (isNewTask()) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mUseCaseHandler.execute(mGetTask, new GetTask.RequestValues(mTaskId),
new UseCase.UseCaseCallback<GetTask.ResponseValue>() {
@Override
public void onSuccess(GetTask.ResponseValue response) {
showTask(response.getTask());
}
@Override
public void onError() {
showEmptyTaskError();
}
});
}
private void showTask(Task task) {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.setTitle(task.getTitle());
mAddTaskView.setDescription(task.getDescription());
}
mIsDataMissing = false;
}
private void showSaveError() {
// Show error, log, etc.
}
private void showEmptyTaskError() {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}
@Override
public boolean isDataMissing() {
return mIsDataMissing;
}
private boolean isNewTask() {
return mTaskId == null;
}
private void createTask(String title, String description) {
Task newTask = new Task(title, description);
if (newTask.isEmpty()) {
mAddTaskView.showEmptyTaskError();
} else {
mUseCaseHandler.execute(mSaveTask, new SaveTask.RequestValues(newTask),
new UseCase.UseCaseCallback<SaveTask.ResponseValue>() {
@Override
public void onSuccess(SaveTask.ResponseValue response) {
mAddTaskView.showTasksList();
}
@Override
public void onError() {
showSaveError();
}
});
}
}
private void updateTask(String title, String description) {
if (isNewTask()) {
throw new RuntimeException("updateTask() was called but task is new.");
}
Task newTask = new Task(title, description, mTaskId);
mUseCaseHandler.execute(mSaveTask, new SaveTask.RequestValues(newTask),
new UseCase.UseCaseCallback<SaveTask.ResponseValue>() {
@Override
public void onSuccess(SaveTask.ResponseValue response) {
// After an edit, go back to the list.
mAddTaskView.showTasksList();
}
@Override
public void onError() {
showSaveError();
}
});
}
}
现在的Presenter不再直接和Data Layer交互,而是使用各种usecase对model进行操作。同样的,其依然负责对view的回调。
总结
至此,本示例的代码结构分析清楚了,在原来mvp的基础上,根据clean的向内依赖的理念,扩展出Domain Layer层,于是就形成了:Presentation Layer 依赖 Domain Layer, Domain Layer 依赖 Data Layer的层次结构。