這是該系列的第五篇,系列文章目錄以下:java
在編程中,抽象意味着「類是部分實現的」。部分實現的類只有等徹底實現後才能實例化。Java 中能夠將方法設置爲抽象的。Kotlin 更上一層樓:屬性也能夠是抽象的。fetch
寫代碼會遇到這樣的場景:類中包含了若干屬性,其中有一些屬性是構造類時必須的,一般會經過構造函數的參數將這些屬性值傳遞進來。另外一些屬性雖然在構造時非必須但在稍後的時間點會用到它,一般會用set()函數來爲這些屬性賦值。動畫
若是忘記調用 set() 會發生什麼?程序會出錯甚至崩潰,這很常見,特別是當別人使用你的類時,他並不知道除了構造對象以外還須要在另外一個地方調用 set() 爲某個屬性賦值,雖然你可能已經把這個潛規則寫在了註釋裏。
那爲何不把這類屬性也做爲構造函數的參數傳入?由於構造的時候屬性值還未準備好。那等它好了在構造對象不行嗎?也不是不能夠,這樣就延後了對象的構建。
有什麼辦法強制使用者必須爲該屬性賦值呢?
在 Java 中類有抽象方法,在構造類對象時強制要求實現,即強制爲行爲賦值。但 Java 中沒有強制爲屬性賦值的特性。Kotlin 的抽象屬性填補了這個空白。
抽象屬性的語法以下:
abstract class A{
abstract val name: String
}
複製代碼
只須要在聲明變量的關鍵詞val
以前加上abstract
,由於屬性是抽象的,因此整個類也變成抽象的。
爲了展現抽象屬性的使用場景,設計了以下這個case:有一個列表用於展現新聞,列表背景會動態變化,好比夏天展現清爽的背景,某地發生地震時展現黑色的背景。
顯然,除了新聞內容,列表背景顏色也得從服務器拉取,若是列表內容先返回則按默認背景色展現列表,當背景顏色返回時刷新下列表。
先定義兩個數據實體類用於存放服務器返回的數據:
//'列表內容'
data class MyBean(val name:String?)
//'列表背景'
data class ColorBean(val color:String?)
複製代碼
列表的數據適配器 Adapter 包含兩個屬性:內容列表和背景顏色,前者是構造時必須參數,後者是非必須的,將非必須的實現爲抽象屬性:
//'內容列表是構造時必要屬性'
abstract class MyAdapter(private val myBean: List<MyBean>?) : RecyclerView.Adapter<MyViewHolder>() {
//'背景顏色是抽象屬性'
abstract val color: ColorBean?
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.my_viewholder, parent, false))
}
override fun getItemCount(): Int { return myBean?.size ?: 0 }
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
//'將內容列表和背景色傳遞給Holder綁定到控件'
myBean?.get(position)?.let { holder.bind(it,color) }
}
}
複製代碼
在 Holder 中若存在顏色屬性則替換列表背景,不然保持其爲 xml 定義的顏色:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(myBean: MyBean?, colorBean: ColorBean?) {
itemView.apply {
colorBean?.run { myBackground.setBackgroundColor(Color.parseColor(color)) }
tvMyViewHolder.text = myBean?.name ?: "no name"
}
}
}
複製代碼
使用 ViewModel + LiveData 存放服務器返回數據:
class MyViewModel : ViewModel() {
//'列表內容'
internal val beanLiveData = MutableLiveData<List<MyBean>>()
//'列表背景色'
internal val colorLiveData = MutableLiveData<ColorBean>()
fun fetchBean() {
//省略了拉取服務器數據
beanLiveData.postValue(value)
}
fun fetchColor() {
//省略了拉取服務器數據
colorLiveData.postValue(value)
}
}
複製代碼
Activity 做爲數據的觀察者:
class MyActivity : AppCompatActivity() {
private val viewModel by lazy { ViewModelProviders.of(this).get(MyViewModel::class.java) }
private var myAdapter: MyAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_activity)
registerObserver()
viewModel.fetchBean()
viewModel.fetchColor()
}
private fun registerObserver() {
viewModel.colorLiveData.observe(this@OverridePropertyActivity, Observer {
//'獲取背景色後刷新列表'
myAdapter?.notifyDataSetChanged()
})
viewModel.beanLiveData.observe(this@OverridePropertyActivity, Observer {
//'獲取列表內容後構建列表適配器實例'
myAdapter = object : MyAdapter(it) {
//'重寫屬性'
override val color: ColorBean?
//'color的值從colorLiveData中獲取'
get() = viewModel.colorLiveData.value
}
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = myAdapter
})
}
}
複製代碼
MyAdapter 是抽象的,在構造實例時得重寫其抽象屬性color
,它是常量,因此只需定義如何獲取屬性,即實現get()
函數,若是是變量還必須定義set()
,就像這樣:
myAdapter = object : MyAdapter(it) {
override var color: ColorBean?
get() = viewModel.colorLiveData.value
set(value) { viewModel.colorLiveData.postValue(value) }
}
複製代碼
其中的object
關鍵詞有不少種用法,它們的共性是「聲明一個類的同時建立一個實例」,文中的用法叫對象表達式,這等同於 Java 中的匿名對象。下面這兩段代碼是等價的:
//'java'
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.v(...)
}
});
//'kotlin'
view.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View?) {
Log.v(...)
}
})
//'kotlin中一般會採用這種更簡單的方式'
view.setOnClickListener { v -> Log.v(...) }
複製代碼
抽象屬性經過關鍵詞abstract
聲明,使用抽象屬性好處多多:
如何獲取值
及如何改變值
。ViewModel
中的LiveData
,但 Adapter 沒有和它們倆耦合。(好吧,java 經過依賴注入也能夠實現這個效果)get()
方法爲屬性賦值,而 java 中調用set()
賦值的時機確定遭遇屬性值被訪問的計時。關鍵詞object
用於聲明一個類的同時構造一個實例。