Sealed Class 密封類(lèi)
如果想對(duì)能夠創(chuàng)建出的子類(lèi)做限制,可以使用密封類(lèi)。
下面一個(gè)例子是沒(méi)有使用密封類(lèi)的:
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(expr: Expr): Int {
return when(expr){
is Sum -> eval(expr.left) + eval(expr.right)
is Num -> expr.value
else -> throw IllegalArgumentException("Unknown Expression")
}
}
這類(lèi)似于只支持加法的抽象語(yǔ)法樹(shù),Expr代表一個(gè)表達(dá)式,也就是語(yǔ)法樹(shù)里的一個(gè)節(jié)點(diǎn),同時(shí)Num代表數(shù)字節(jié)點(diǎn),它只可能是葉子,Sum代表加法節(jié)點(diǎn),不可能是葉子。
現(xiàn)在如果我們要實(shí)現(xiàn)eval函數(shù)來(lái)計(jì)算抽象語(yǔ)法樹(shù)的最終結(jié)果,我們發(fā)現(xiàn),始終需要一個(gè)else來(lái)收尾,因?yàn)镋xpr可能還有其他實(shí)現(xiàn)類(lèi),可能既不是Sum又不是Num,盡管代碼里根本沒(méi)有其他實(shí)現(xiàn)類(lèi)。
密封類(lèi)能解決這個(gè)問(wèn)題。
sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(expr: Expr): Int {
return when(expr){
is Expr.Sum -> eval(expr.left) + eval(expr.right)
is Expr.Num -> expr.value
}
}
密封類(lèi)表明了該類(lèi)不可能有除了Num和Sum之外的其他子類(lèi),所以編譯器可以發(fā)現(xiàn)我們when中的代碼是無(wú)懈可擊的,自然不用一個(gè)額外的else。
類(lèi)委托
Java中有一套設(shè)計(jì)模式就是委托模式,就是指編寫(xiě)一個(gè)類(lèi),但它不提供實(shí)現(xiàn),所有的功能都會(huì)委托給另一個(gè)類(lèi)實(shí)現(xiàn),在必要的時(shí)候?qū)︻?lèi)進(jìn)行增強(qiáng)。Java后面的代理、動(dòng)態(tài)代理技術(shù)全部都是基于委托實(shí)現(xiàn)的,可以說(shuō)它是Java世界的一個(gè)支柱。
Kotlin默認(rèn)支持委托,不像Java,要么用IDE生成一大堆代碼,要么在編譯期使用其他動(dòng)態(tài)代理工具生成,Kotlin默認(rèn)提供了by關(guān)鍵字。
下面的類(lèi)繼承自MutableCollection,但它完全不存儲(chǔ)數(shù)據(jù),而是通過(guò)by委托給innerSet。
class CountingSet<T> (
val innerSet: MutableCollection<T> = HashSet<T>()
) : MutableCollection<T> by innerSet{
var objectsAdded = 0
override fun add(element: T): Boolean {
objectsAdded++
return innerSet.add(element)
}
override fun addAll(elements: Collection<T>): Boolean {
objectsAdded += elements.size
return innerSet.addAll(elements)
}
}
fun main(){
val set = CountingSet<Int>()
set.add(10)
set.addAll(listOf(1,2,4,15,5,3))
set.remove(10)
println(set.objectsAdded)
}
比較常用的就是委托類(lèi)作為構(gòu)造器參數(shù)傳入,Java中比較常見(jiàn)的就是基于委托的IO流,我們經(jīng)常這樣寫(xiě):
new BufferedInputStream(new FileInputStream(...));
這里BufferedInputStream并不會(huì)自己實(shí)現(xiàn)InputStream的讀取功能,而是委托給FileInputStream并對(duì)它的功能進(jìn)行增強(qiáng)(通過(guò)建立緩沖區(qū))。
我們上面編寫(xiě)的類(lèi)也是,你可以調(diào)用CountingSet傳入不同的Collection實(shí)現(xiàn),不同的是我們提供了一個(gè)默認(rèn)值。
除了使用構(gòu)造器參數(shù),還可以直接新建一個(gè)類(lèi)委托,因?yàn)橛袝r(shí)候我們就想讓它委托同一個(gè)類(lèi),不想讓用戶自己抉擇。
class MySet<T> () :
MutableCollection<T> by HashSet<T>(){
}
屬性委托
Jetpack Compose中有一個(gè)記錄狀態(tài)并自動(dòng)更新UI的東西,就是var value by remember,這種監(jiān)測(cè)數(shù)據(jù)更新并自動(dòng)刷新UI的東西在如今數(shù)據(jù)驅(qū)動(dòng)的框架中并不少見(jiàn)。Jetpack Compose就是通過(guò)屬性委托來(lái)實(shí)現(xiàn)的數(shù)據(jù)監(jiān)測(cè)。
class Remember{
lateinit var name: String
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
// return name
return name
}
operator fun setValue(thisRef: Any?, property: KProperty<*>,value: String) {
// name = value
println("${property.name} is changed!!!")
name = value
}
}
fun main(){
var name: String by Remember()
name = "ASDFASDF"
println(name)
}
/*
name is changed!!!
ASDFASDF
*/
被委托的類(lèi)應(yīng)該實(shí)現(xiàn)一個(gè)getValue和setValue方法,委托方的變量不再存儲(chǔ)值,而是由被委托的類(lèi)提供存儲(chǔ)功能。
我們接下來(lái)編寫(xiě)一個(gè)懶加載的屬性委托,就是第一次訪問(wèn)屬性時(shí)才為屬性賦值
class LazyDelegate<T>(private val compute: ()->T){
var t: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (t == null) t = compute()
return t!!
}
operator fun setValue(thisRef: Any?, property: KProperty<*>,value: T) {
t = value
}
}
fun <T> lazy(compute: ()->T) = LazyDelegate<T>(compute)
fun main(){
var name: String by lazy {
"HelloWorld"
}
println(name)
}
這一次我們提供了一個(gè)lazy方法,Lazy方法會(huì)返回我們的委托人LazyDelegate,因?yàn)镵otlin官方就為一些自帶的委托封裝了方法,可能是Kotlin社區(qū)慣用的編碼規(guī)范,確實(shí),這樣好看一些,而且Jetpack Compose中的remember實(shí)際上也是這樣寫(xiě)的。
然后,我們還運(yùn)用了泛型和lambda表達(dá)式,lambda用于返回一個(gè)值,一般使用懶加載的時(shí)候,這個(gè)lambda表達(dá)式都會(huì)是一個(gè)很復(fù)雜并且可能并不常用的運(yùn)算,所以這樣如果這個(gè)值如果沒(méi)被需要,懶加載就不會(huì)執(zhí)行。泛型用于支持全部類(lèi)型的值。
伴生對(duì)象
Java中經(jīng)常會(huì)使用靜態(tài)工廠方法來(lái)構(gòu)造對(duì)象,這是因?yàn)殪o態(tài)工廠方法比構(gòu)造器更加適用于處理那些很多屬性可以不在構(gòu)造時(shí)提供的類(lèi)。靜態(tài)工廠方法更加具有可讀性。Kotlin根本沒(méi)有靜態(tài)這一說(shuō),Kotlin代替靜態(tài)的辦法一個(gè)是object,一個(gè)是頂層函數(shù)。但這倆都不適用于靜態(tài)工廠,因?yàn)殪o態(tài)工廠經(jīng)常要訪問(wèn)類(lèi)中的私有成員。
伴生對(duì)象是用來(lái)干這些的。
class Person private constructor(
name: String?,
age: Int?,
address: String?
){
companion object {
fun fromNameAndAge(name: String, age: Int): Person = Person(name,age,null)
fun fromName(name: String): Person = Person(name,null,null)
fun fromNameAndAddress(name: String, address: String): Person = Person("Lucy",null,address)
fun newPerson(name: String, age: Int, address: String): Person = Person(name,age,address)
}
}
fun main(){
Person.fromNameAndAge("Lucy",12)
}
這對(duì)于Builder模式同樣適用,對(duì)于絕大多數(shù)需要和類(lèi)中私有成員進(jìn)行交互的地方,都適用。
但是,別忘了Kotlin中的命名參數(shù),上面的例子本可以用命名參數(shù)更加方便的解決。
class Person constructor(
name: String,
age: Int? = null,
address: String? = null
)
fun main(){
Person(name = "Lucy", age = 12)
}
當(dāng)然伴生對(duì)象可以命名
class Person constructor(
name: String,
age: Int? = null,
address: String? = null
){
companion object Loader{
fun fromJson(json: String): Person{ ... }
}
}
fun main(){
Person.Loader.fromJson()
}
伴生對(duì)象也可以有擴(kuò)展函數(shù),這是因?yàn)橄裆厦娴腖oader這種伴生對(duì)象和類(lèi)中的邏輯關(guān)系不大,分離到外部可以實(shí)現(xiàn)關(guān)注點(diǎn)分離。
class Person constructor(
name: String,
age: Int? = null,
address: String? = null
){
companion object Loader{}
}
fun Person.Loader.fromJson(json: String): Person {
...
}
fun main(){
Person.Loader.fromJson()
}
如果是沒(méi)有名字的伴生對(duì)象,也可以
fun Person.Companion.fromJson(json: String): Person{
...
}