从Google Sample的Model层设计开始
最近准备动手写个新项目,在构思项目结构的同时,又翻出了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主要是怎么构成的。嗯,体积庞大其实也就三个部分而已,名称标注简单明了,我就翻译凑个字数了。
- Remote Data Source 远程数据源
- Local Data Source 本地数据源 使用SQLite管理
- 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进行学习。
当然,学习是为了更好的运用,到了自己的项目中,也不必生搬硬套,没有最好只有最合适的。希望大家都能有所收获~