Kotlin基礎:抽象屬性的應用場景

這是該系列的第五篇,系列文章目錄以下:java

  1. Kotlin基礎:白話文轉文言文般的Kotlin常識編程

  2. Kotlin基礎:望文生義的Kotlin集合操做bash

  3. Kotlin實戰:用實戰代碼更深刻地理解預約義擴展函數服務器

  4. Kotlin實戰:使用DSL構建結構化API去掉冗餘的接口方法app

  5. Kotlin基礎:屬性也能夠是抽象的ide

  6. Kotlin進階:動畫代碼太醜,用DSL動畫庫拯救,像說話同樣寫代碼喲!函數

  7. Kotlin基礎:用約定簡化相親post

在編程中,抽象意味着「類是部分實現的」。部分實現的類只有等徹底實現後才能實例化。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

其中的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聲明,使用抽象屬性好處多多:

  1. 第一個好處是強制性,它強制在構建對象時必須定義如何獲取值如何改變值
  2. 第二個好處是解耦,文中 Adapter 中背景色的值來自於ViewModel中的LiveData,但 Adapter 沒有和它們倆耦合。(好吧,java 經過依賴注入也能夠實現這個效果)
  3. 第三個好處是惰性加載,只有當背景色被引用的時候纔會去調用其get()方法爲屬性賦值,而 java 中調用set()賦值的時機確定遭遇屬性值被訪問的計時。

關鍵詞object用於聲明一個類的同時構造一個實例。

相關文章
相關標籤/搜索