Android架构分析【三】mvp-cleanClean Architecture代码分析总结

架构简介中,最重要的是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的层次结构。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

android性能优化1

一、在使用Gallery控件时,如果载入的图片过多,过大,就很容易出现OutOfMemoryError异常,就是内存溢出。这是因为Android默认分配的内存只...

2216
来自专栏学海无涯

Android开发之自动填充短信验证码

笔者发现在很多应用中,都有自动获取验证码的功能:点击获取验证码按钮,收到短信,当前应用不需要退出程序就可以获取到短信中的验证码,并自动填充。觉得这种用户体验很赞...

4668
来自专栏开发之途

Android IPC机制(2)-AIDL

1694
来自专栏西二旗一哥

iOS - autoreleasepool and @autoreleasepool

+ 在一个自动引用计数的环境中(并不是垃圾回收机制),一个包含了多个对象的 NSAutoreleasePool 对象能够接收 autorelease 消息并且...

1694
来自专栏Android干货

安卓开发_深入理解Handler消息传递机制

3006
来自专栏非著名程序员

Android 内存泄露简介、典型情景及检测解决

什么是内存泄露? Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到...

2138
来自专栏学海无涯

Android开发之那些好用的数据结构与API(三)

之前的系列文章 Android开发之那些好用的数据结构与API 和Android开发之那些好用的数据结构与API(二)中,已经介绍了一些,趁热打铁,本次介绍的内...

34711
来自专栏技术小黑屋

啰嗦一下android中的NetworkOnMainThreadException

相信很多Android开发者很多都遇到过android.os.NetworkOnMainThreadException 这个异常,意思就是主线程进行网络操作异常...

4472
来自专栏Java架构师学习

分布式消息队列Apache RocketMQ源码剖析-Producer分析正文总结

正文 首先我们看一下Producer的继承结构: ? image.png MQAdmin主要包含一些管理性的接口,比如创建topic、查询某个特定消息以方便排查...

4167
来自专栏Android先生

RxJava2 实战知识梳理(6) - 基于错误类型的重试请求

在网络请求时,有时候会出现需要进行重试的情况,重试的时候,有以下几点需要注意:

1221

扫码关注云+社区

领取腾讯云代金券