这是该系列的第五篇,系列文章目录以下: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
用于声明一个类的同时构造一个实例。