我们已经创建了基于数据库的INoteRepository实现,也就是NoteRepository类。现在可以将数据仓库从TestNoteRepository切换到NoteRepository了。
我们固然可以简单的将原来使用TestNoteRepository类的位置全部替换成NoteRepository类,再稍作修改即可完成切换。
但是,考虑以下情形:
- 假设我们在后续工作中,增加了大量的其它功能,在数量多得记不清的位置用到了数据访问。
- 紧接着我们又决定在不同设备之间共享笔记数据,要把数据从本地数据库存储改为云端存储,也就是说,要再创建一个基于云存储的数据仓库实现。
这样,我们又要再次劳心劳力去寻找哪些地方调用了数据仓库操作,然后一一替换。这样做事耗费体力并容易出错。因此,我们需要集中在一个单独的位置来决定采用何种数据仓库实现。我们之前用到了设计模式中的单例模式,现在考虑另外一种——工厂模式(Factory Pattern)。
1. 通过App的构建类型(Build Type)来选择数据仓库实现
在着手实现工厂模式之前,我们需要设置一个全局性的标志,作为将来的工厂选择采取何种数据仓库实现的依据。这一标志可以通过修改build.gradle文件中的buildTypes配置来实现。
打开app目录下的build.gradle文件(不是项目根目录下的同名文件),找到其中的buildTypes(构建类型)配置,应当是类似下面的内容:
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
可以看到buildTypes配置项中目前包含一个名叫release的子项。其实它还缺省包含一个debug子项,二者分别用于构建App的发布(release)版本和调试(debug)版本。我们在开发工程中编译执行的是默认的调试版本。
我们现在做以下几个步骤的工作:
- 自定义一个新的构建类型,名叫testRepo
- 分别为debug,release和testRepo增加一个相同名称的构建配置域(buildConfigField),命名为“USE_TEST_REPO”,类型为String类型,并且在debug和release类型下将值设为“database”,而在testRepo类型下设置为“test”。完成之后,buildTypes内容为:
buildTypes {
debug {
buildConfigField "String", "USE_TEST_REPO", '"database"'
}
release {
buildConfigField "String", "USE_TEST_REPO", '"database"'
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
repoTest {
buildConfigField "String", "USE_TEST_REPO", '"test"'
}
}
经过这次修改,编辑器上方会出现类似下面的黄色提示条:
点击最右端的“Sync Now”以同步配置。然后选择主菜单上的“Navigate -> Class...”,在弹出的对话框中输入“BuildConfig”,如下图:
回车后编辑器自动跳转到一个名叫BuildConfig的类。这个类比较特殊,并不是我们自己创建和编写的,而是Android Studio开发环境根据build.gradle中的配置自动生成的,并且不允许修改。我们可以通过这个类中定义的常量读取当前构建类型相关的配置参数。
我们可以看到,前面步骤中添加的构建配置于USE_TEST_REPO作为常量出现在了BuildConfig类的末尾,并且值为false:
public final class BuildConfig {
...
// Fields from build type: debug
public static final String USE_TEST_REPO = "database";
}
因为开发环境缺省的构建类型就是debug,所以按照我们在debug类型中给定的值来配置。为了加深认识,我们切换到我们自定义的testRepo类型来看看会出现什么情况。沿着Android Studio窗口最左侧向下找到“Build Variants”标签,点击使其展开,应当类似下图所示:
可以看到,当前的“Build Variant”类型确实是debug。点击debug,在展开的下拉列表中选择“testRepo”:
将BuildConfig.java文件关闭,然后重新打开(按照前面的方法),会看到USE_TEST_REPO常量的值变成了“test”:
public final class BuildConfig {
...
// Fields from build type: repoTest
public static final String USE_TEST_REPO = "test";
}
因此,我们可以通过切换构建类型来获得配置有所不同的APK版本。这项技术常用于不同渠道版本(如360应用市场,百度应用市场或豌豆荚等)的批量打包生成。这里不展开讨论。
关闭BuildConfig.java文件,并将“Build Variant”切回debug类型。
下面我们正式开始编写工厂类。
2. 实现工厂类NoteRepositoryFactory
在repository包下新建名为NoteRepositoryFactory的类,并添加公共静态方法getNoteRepository():
public class NoteRepositoryFactory {
public static INoteRepository getNoteRepository(Context context) {
...
}
}
在getNoteRepository()方法中,我们读取当前构建类型下的USE_TEST_REPO常量,以它的值为依据来决定使用何种实现:
- “database”:选择NoteRepository类作为实现
- “test”:选择TestNoteRepository类作为实现
将来如果根据需求增加其它实现,也只需修改build.gradle文件和NoteRepositoryFactory类,而不必在UI部分的代码漫山遍野的寻找。
具体的代码实现如下:
public static INoteRepository getNoteRepository(Context context) {
switch (BuildConfig.USE_TEST_REPO) {
case "test":
return TestNoteRepository.getInstance();
case "database":
default:
return NoteRepository.getInstance(context);
}
}
这样,一个简单的工厂类就创建完成了。
下面用工厂方法替换程序代码中各处获取数据仓库实例的操作:
(1)全部笔记页面
找到如下的代码:
// 笔记数据仓库接口,并绑定到测试仓库
private INoteRepository noteRepository = TestNoteRepository.getInstance();
修改为:
// 笔记数据仓库接口,并绑定到测试仓库
private INoteRepository noteRepository;
并改在onCreate()方法中靠前的位置进行初始化:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note_list);
noteRepository = NoteRepositoryFactory.getNoteRepository(getApplicationContext());
...
}
(2)新建笔记页面
做法与全部笔记页面类似,不再赘述。
不过在早期编写代码过程中,存在不规范的地方,需要进行修改。找到onFinishEdit()方法,其实现如下:
private void onFinishEdit() {
// 1. 生成id
long id = noteRepository.getAllNotes().size() + 1;
// 2. 从编辑区获取标题和内容字符串
String title = mTitleEdit.getEditableText().toString();
String content = mContentEdit.getEditableText().toString();
// 3. 创建笔记对象
Note note = new Note(id, title, content, System.currentTimeMillis());
// 4. 存储笔记
noteRepository.saveNote(note);
Toast.makeText(this, R.string.msg_note_saved, Toast.LENGTH_SHORT).show();
finish(); // 关闭窗口
}
这段代码中首先生成了一个id。但是在基于数据库的实现中,并不需要我们手动生成这个id,数据库会自动完成这一操作。因此改写代码如下:
private void onFinishEdit() {
// 1. 从编辑区获取标题和内容字符串
String title = mTitleEdit.getEditableText().toString();
String content = mContentEdit.getEditableText().toString();
// 2. 创建笔记对象
Note note = new Note(0, title, content, System.currentTimeMillis());
// 3. 存储笔记
if (noteRepository.saveNote(note) != null) {
Toast.makeText(this, R.string.msg_note_saved, Toast.LENGTH_SHORT).show();
finish(); // 关闭窗口
} else {
Toast.makeText(this, R.string.msg_note_not_saved, Toast.LENGTH_SHORT).show();
}
}
(3)阅读笔记页面
这一页的修改与全部笔记页面几乎一样,请参照改动。
3. 分层架构
到此为止,数据库与用户交互模块的对接已经完成。在设计、开发过程中,我们实现了一个简单的分层架构(architecture):
实际的软件开发中经常采用这样的分层架构(实际上是更为精细的分层架构),以实现高内聚、低耦合的模块设计,使软件结构清晰,方便软件的测试、维护和扩展。
在模拟器上运行程序,最终效果如下: