从Google Sample的Model层设计开始

作者: 岱zy | 来源:发表于2017-08-03 19:03 被阅读579次

    最近准备动手写个新项目,在构思项目结构的同时,又翻出了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设计模式系列

    相关文章

      网友评论

        本文标题:从Google Sample的Model层设计开始

        本文链接:https://www.haomeiwen.com/subject/ryyplxtx.html