在大型项目中,找到具有固定“模式”的类并不罕见,这些模式指定了一个类应该如何工作,我们称这些类为“标记类(tagged class)”,因为它们包含指定其操作模式的标记。它们存在许多问题,而这些问题大多源于不同模式的不同职责在同一层级中相互争夺,尽管它们通常是可以区分的。例如,在下面代码块中,我们可以让这个类来测试 value 是否满足某个条件,这个例子是简单的,但它是一个来自大型项目的真实案例:
class ValueMatcher<T> private constructor(
private val value: T? = null,
private val matcher: Matcher
){
fun match(value: T?) = when(matcher) {
Matcher.EQUAL -> value == this.value
Matcher.NOT_EQUAL -> value != this.value
Matcher.LIST_EMPTY -> value is List<*> && value.isEmpty()
Matcher.LIST_NOT_EMPTY -> value is List<*> && value.isNotEmpty()
}
enum class Matcher {
EQUAL,
NOT_EQUAL,
LIST_EMPTY,
LIST_NOT_EMPTY
}
companion object {
fun <T> equal(value: T) = ValueMatcher<T>(value = value, matcher = Matcher.EQUAL)
fun <T> notEqual(value: T) = ValueMatcher<T>(value = value, matcher =
Matcher.NOT_EQUAL)
fun <T> emptyList() = ValueMatcher<T>(matcher = Matcher.LIST_EMPTY)
fun <T> notEmptyList() = ValueMatcher<T>(matcher =
Matcher.LIST_NOT_EMPTY)
}
}
sealed class ValueMatcher<T> {
abstract fun match(value: T): Boolean
class Equal<T>(val value: T) : ValueMatcher<T>() {
override fun match(value: T): Boolean =
value == this.value
}
class NotEqual<T>(val value: T) : ValueMatcher<T>() {
override fun match(value: T): Boolean =
value != this.value
}
class EmptyList<T>() : ValueMatcher<T>() {
override fun match(value: T) =
value is List<*> && value.isEmpty()
}
class NotEmptyList<T>() : ValueMatcher<T>() {
override fun match(value: T) =
value is List<*> && value.isNotEmpty()
}
}
我们不一定需要使用密封类。我们可以使用 abstract 来代替,但是密封禁止在该文件之外定义任何子类。正因如此,如果我们在 when 中涵盖了其所有子类型,我们就不需要添加 else 分支,因为它能保证是齐全的。利用这个优势,我们可以很容易地添加新功能,并且知道不会忘记在这些 when 语句中去包含它们。
这是一种很便利的方式,可以定义不同模式下具有不同的行为操作。例如,我们可以使用 when 将 reversed 定义为扩展函数,而不用在所有的子类中定义这个函数。新的功能可以以这种方式添加到密封类中,甚至可以作为扩展函数:
fun <T> ValueMatcher<T>.reversed(): ValueMatcher<T> =
when (this) {
is ValueMatcher.EmptyList -> ValueMatcher.NotEmptyList<T>()
is ValueMatcher.NotEmptyList -> ValueMatcher.EmptyList<T>()
is ValueMatcher.Equal -> ValueMatcher.NotEqual(value)
is ValueMatcher.NotEqual -> ValueMatcher.Equal(value)
}