android故事累积Android开发Android 技术开发

从Google Sample的Model层设计开始

2017-08-03  本文已影响579人  岱zy

最近准备动手写个新项目,在构思项目结构的同时,又翻出了google的设计模式Demo回忆了一遍。
之前只是学习Mvp大概结构,没有细究,这次又看了一遍,又有很大的收获。
尤其是在Model层的结构上,又给了自己许多灵感。不得不佩服google的工程师,就算是个demo,各种功能逻辑也是设计的相当规范。

文末附上google设计模式系列demo链接

为什么Model层如此重要

如标题,准备详细记录一下Todo-MVP中Model层的结构和设计流程,就不太多描述其它设计模式相关的内容了。简单介绍下。

Model是 MVP模式中的 M(同MVC中的M)。即模型层,通常用来处理数据库,以及其它数据相关的操作。

发现很多初次接触设计模式的同学往往会纠结各层级的关系,尤其是到了MVP,简单的问题反而多写了许多类。非但没体会到设计模式的优势,还徒增烦恼。其实没必要纠结,通常先理清了单一层所做的事情,其它层级的分工也很容易去理解了。理解了Model层的设计理念,也有助于理解设计模式中各个层级的业务和关系

先上一张今天主要要学习的Todo-MVP官方结构图。

用红色框住的部分是今天的主角Model层

看吧,其实就按Modle层在图中占得比重,那也是妥妥的主角啊!不仅如此,从MVC到MVP,再到MVVM,改朝换代,总是有Model层立足之地。Model层的独立,在任何设计模式中都是相当重要的一部分

Google Sample中的Model层结构

撇开右边的View和Presenter,来看看体积庞大的Model主要是怎么构成的。嗯,体积庞大其实也就三个部分而已,名称标注简单明了,我就翻译凑个字数了。

  1. Remote Data Source 远程数据源
  2. Local Data Source 本地数据源 使用SQLite管理
  3. Repository 仓库,管理调用远程和本地数据,在内存中

而根据结构图中的箭头, 我们可以看出Repository直接和Presenter接触,算是扛起了了Modle层的大旗。统一所有的数据操作,并提供一个Repository来与外界交互,这个大概就是Model层的实现思路了。这样的好处是能把数据相关的操作完全拆分出来,作为一个独立的模块,结构清晰的同时还具有非常高的复用性。

Google Sample中Model层的优势

实际上关于Model层的划分,大家应该都略有心得。对于Slqite,通常都会针对性封装一个操作类,在Activity中更好的调用。而RemoteData,实际上在之前的开发中,确实没有将Local和Remote统一管理的经验和意识,只是针对网络请求进行一定的封装,没有能够深入到Model的层次。这大概是我在这个demo中比较大的收获了。

举个简单例子比如我们有一个Activity展示列表数据,每次打开的时候先直接展示缓存的本地数据,然后有网络的话,马上向服务器请求数据,如果有新的就刷新页面,更新本地缓存,没有,就不做变化。

假设按我之前的思路去处理,大概会有一个LocalDataUtil.getList()来做本地数据的获取,然后还有一个HttpUtil.requestList(),嗯,通常这里是回调获取数据。这样,我们的Activity已经依赖了两个对象,LocalDataUtil和HttpUtil,我们还有在Activity中先 setLocalData(),然后HttpCallback回调结果了,判断是否需要刷新 isNeedUpdate(),最后才来refreshAdapter()刷新一下页面。

补充好业务中的细节处理,也许还会洋洋自得写出了一份优质代码。似乎功能划分很清晰,却没想到其实还可以做到更好。

多学习思考,眼界的开阔和经验的丰富往往会让你有更多更好的灵感。编程无止境。

这时候,大概就能体会到Model层完全独立,将Local和remote统一管理的好处了,不论前端如何变化,到了数据,不外乎增删查改,管你怎么展示数据修改界面和载体,从Activity切到Fragment,Model层只需要提供一个Repository.getList()就行了,不需要携家带口来一次大迁移以及承担代码变动潜藏的新隐患。

Google Sample中Modle的实现细节

先贴图,todo-mvp中modle层的完整结构。

Model层整体在data包下,也算是体现了model负责处理数据相关内容的特性。
其下是Task,这个使我们需要操作的数据对象。然后子包Source里,就是模型层操作相关的内容了。我们可以很轻松的和之前的结构图对应起来。

Local ,Remote 以及Repository。

这里我们可以先把Local包中的TasksDBHelper和TasksPersistenceContract先排除开来。如类名所示,前者继承SQLiteOpenHelper,用于数据库的初始化和建表。后者名称大概可以理解为数据持久化契约,其中定义了一个继承BaseColums的抽象静态内部类TaskEntry,对应Task对象用于描述表对象的列名。

之后,我们可以看到有类TasksLocalDataSource,TasksRemoteDataSource,TasksRepository以及它们实现的接口,TasksDataSource。在这个Todo-mvpSample中,同样适用Contract作为接口首先约定了View和Presenter相关的方法,然后通过实现接口在进一步处理。非常喜欢这样的代码写法,先写好接口,代码查看起来非常清晰,也能明确需要处理的业务功能,方便修改。

把TasksDataSource贴出来给大家感受一下

/**
 * Main entry point for accessing tasks data.
 * <p>
 * For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other
 * methods to inform the user of network/database errors or successful operations.
 * For example, when a new task is created, it's synchronously stored in cache but usually every
 * operation on database or network should be executed in a different thread.
 */
public interface TasksDataSource {

    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }

    interface GetTaskCallback {

        void onTaskLoaded(Task task);

        void onDataNotAvailable();
    }

    void getTasks(@NonNull LoadTasksCallback callback);

    void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);

    void saveTask(@NonNull Task task);

    void completeTask(@NonNull Task task);

    void completeTask(@NonNull String taskId);

    void activateTask(@NonNull Task task);

    void activateTask(@NonNull String taskId);

    void clearCompletedTasks();

    void refreshTasks();

    void deleteAllTasks();

    void deleteTask(@NonNull String taskId);
}

带着注释一起,是不是非常简单明了。直接把项目中需要用到的所有数据操作都直接通过接口定义了出来。还考虑到数据请求的问题连回调都提前设计好了。

Local和Remote分别通过一个类实现这个接口。两边持有完全一样的方法,只是在实现细节上区分开来,local通过Sqlite来操作本地数据,remote则通过Http来获取网络数据(这个Sample中通过Handle模拟远程数据)。

如果有需求新的方法,在接口中加上,两边再implement Methods就可以了,非常方便。

Repository同样实现了这个接口,但是此时它已经不负责数据的操作了,作为与外界交互的直接部分,通过持有Local和Remote,负责提供从两者间挑选出来的数据,以及同时操作两者进行数据更新保存删除等操作。是的Repository其实就是一层外壳,内部操作Local和Remote,并进一步的业务处理。

用Repository中的getTask()方法举例。

  /**
     * Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
     * uses the network data source. This is done to simplify the sample.
     * <p>
     * Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if both data sources fail to
     * get the data.
     */
    @Override
    public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
        checkNotNull(taskId);
        checkNotNull(callback);

        Task cachedTask = getTaskWithId(taskId);

        // Respond immediately with cache if available
        if (cachedTask != null) {
            callback.onTaskLoaded(cachedTask);
            return;
        }

        // Load from server/persisted if needed.

        // Is the task in the local data source? If not, query the network.
        mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
            @Override
            public void onTaskLoaded(Task 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);
                callback.onTaskLoaded(task);
            }

            @Override
            public void onDataNotAvailable() {
                mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
                    @Override
                    public void onTaskLoaded(Task 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);
                        callback.onTaskLoaded(task);
                    }

                    @Override
                    public void onDataNotAvailable() {
                        callback.onDataNotAvailable();
                    }
                });
            }
        });
    }

   private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
        mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                refreshCache(tasks);
                refreshLocalDataSource(tasks);
                callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            }

            @Override
            public void onDataNotAvailable() {
                callback.onDataNotAvailable();
            }
        });
    }

注释写的非常清楚,整体逻辑就是先从缓存中获取,如果有,直接返回结果,这是最高效的方式。其次,没有就从本地数据库检索,本地数据不可用的情况下,最后才通过网络获取数据。得到数据结果同时更新缓存以及本地数据。

整个流程相当高效严谨,节约了不必要的操作,操作数据的同时也充分考虑到性能的优化。这些在平常的开发中往往是容易忽略的地方。而Repository中的其它方法也大体类似,主要是优化细节处理,完善数据操作的逻辑。从而更优的与外层进行数据交互。

Google Sample中Model层的调用

作为一个MVP结构中的Model层。依照结构图,Repository被Presenter持有,Presenter同时也持有View层。即Precenter通过Repository进行数据操作,再依据数据操作的结果通知View层进行页面刷新

这里,大家应该就对Model层中Repository的主要作用以及MVP各层级的关系比较明确了,也就不详细赘述了。有兴趣的可以自己下载Google设计模式相关demo进行学习。

当然,学习是为了更好的运用,到了自己的项目中,也不必生搬硬套,没有最好只有最合适的。希望大家都能有所收获~

Google设计模式系列

上一篇下一篇

猜你喜欢

热点阅读