空对象模式(Null Object Pattern),指通过特定的、没有负载有效数据的空对象,表示实例不存在的默认状态。可用在不能接受 null 的逻辑,或者利用空对象实现区别于实例为空的应用场景。
这是一般场景的用户模型,包含用户id和昵称:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User(private val userId: String,
private val roomId: String,
private val nickname: String) {
fun getUserId(): String {
return userId
}
fun getRoomId(): String {
return roomId
}
fun getRemark(): String {
return nickname
}
}
实现空对象的形式有很多,比较正式的实现方法是:子类继承父类并重写所有可见成员方法,通过抛出异常的方式,阻止代码调用空对象方法。这样做的目的是强调不要通过空对象获取无效数据。
这种方式在父类有很多可重写方法是会略显麻烦,可自行约束对空对象的使用达到相同目的,或不妨考虑使用动态代理,代理所有方法并抛出同一个提示。
1
2
3
4
5
6
7
8
9
10
11
class UserNull() : User("", "", "") {
// 重写父类方法,禁止通过空对象实例获取无效数据
override fun getUserId(): String {
throw IllegalAccessException("Access to this method is denied.")
}
override fun getRemark(): String {
throw IllegalAccessException("Access to this method is denied.")
}
}
当然,父类可(根据实际)允许子类重写部分父类方法,以下是 User 方法使用 open 修饰允许重写。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
open class User(private val userId: String,
private val roomId: String,
private val nickname: String) {
open fun getUserId(): String {
return userId
}
open fun getRoomId(): String {
return roomId
}
open fun getRemark(): String {
return nickname
}
// 同时增加此方法判断实例是否为空对象
fun isNullObject(): Boolean {
return this == User.USER_NULL_OBJ
}
companion object {
// 定义单例空对象,避免创建多余实例
val USER_NULL_OBJ = UserNull()
}
}
对于 RxJava 这种不能接受 null 对象的应用场景来说,使用空对象表达 null 最合适不过。查不到用户时使用 空对象 代替 null,既能利用 filter 提前结束操作,又能避免 fromCallable 返回 null 引起空指针异常堆栈跟踪的性能损耗。
1
2
3
4
5
Observable.fromCallable { UserDao.load(userId) ?: User.USER_NULL_OBJ }
.filter { !it.isNullObject() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { removeUser(it.userId) }