Kotlin 基础 | 委托及其应用

简介: Kotlin 基础 | 委托及其应用

委托是常见的模式,它和编程语言无关,即把本来自己做的事情委托给另一个对象去做。装饰者模式和代理模式都通过委托复用了行为。Kotlin 在语言层面支持了委托,这一篇结合实例介绍一下 Kotlin 的委托。


Kotlin 的装饰者模式


装饰者模式和继承拥有相同的目的,都是为了扩展类,只不过它运用了更复杂的方式通:继承 + 组合。装饰者模式在复用原有类型和行为的基础上为其扩展功能。关于装饰者模式和代理模式的详细分析可以点击使用组合的设计模式 | 追女孩要用的远程代理模式


下面是装饰者模式的实例:


interface Accessory {
    fun name(): String // 配件名字
    fun cost(): Int //  配件价格
    fun type(): String // 配件类别
}


这个接口用来描述一个抽象的配件,一个具体的配件需要实现三个方法,分别来定义配件名字、价格、类别。


羽毛、戒指、耳环是3个具体的配件,它的实现如下:


class Feather: Accessory{
    override fun name(): String = "Feather"
    override fun cost(): Int  = 20
    override fun type(): String  = "body accessory"
}
class Ring: Accessory{
    override fun name(): String = "Ring"
    override fun cost(): Int  = 30
    override fun type(): String  = "body accessory"
}
class Earrings: Accessory{
    override fun name(): String = "Earrings"
    override fun cost(): Int  = 15
    override fun type(): String  = "body accessory"
}


现需要新增羽毛戒指和羽毛耳环,按照继承的思想可以这样实现:


class FeatherRing: Accessory{
    override fun name(): String = "FeatherRing"
    override fun cost(): Int  = 35
    override fun type(): String  = "body accessory"
}
class FeatherEarrings: Accessory{
    override fun name(): String = "FeatherEarrings"
    override fun cost(): Int  = 45
    override fun type(): String  = "body accessory"
}


这样写的缺点是只复用了类型,没复用行为。每次新增类型的时候都得新增一个子类,会造成子类膨胀。若改用装饰者模式,则可以减少一个子类:


class Feather(private var accessory: Accessory) : Accessory {
    override fun name(): String = "Feather" + accessory.name()
    override fun cost(): Int = 20 + accessory.cost()
    override fun type(): String  = accessory.type()
}


现在羽毛戒指和耳环分别可以这样表达Feather(Ring())Feather(Earrings())


Feather运用组合持有了一个抽象的配件,这样被注入配件的行为就得以复用。name()cost()在复用行为的基础上追加了新的功能,而type()直接将实现委托给了accessory


运用 Kotlin 的委托语法可以进一步简化Feather类:


class Feather(private var accessory: Accessory): Accessory by accessory {
    override fun name(): String = "Feather" + accessory.name()
    override fun cost(): Int = 20 + accessory.cost()
}


by 关键词出现在类名后面,表示类委托,即把类的实现委托一个对象,该对象必须实现和类相同的接口,在这里是Accessory接口。使用by的好处是消灭模板代码,就如上面所示,type()接口的实现就可以省略。


惰性初始化一次


惰性初始化也是一种常见的模式:延迟对象的初始化,直到第一次访问它。当初始化消耗大量资源,惰性初始化显得特别有价值。


支持属性是一种实现惰性初始化的惯用技术:


class BitmapManager {
    // 支持属性用于存储一组 Bitmap
    private var _bitmaps: List<Bitmap>? = null
    // 供外部访问的一组 Bitmap
    val bitmaps: List<Bitmap>
        get() {
            if (_bitmaps == null) {
                _bitmaps = loadBitmaps()
            }
            return _bitmaps!!
        }
}


支持属性_bitmaps是私有的,它用来存储一组 Bitmap,而另一个同样类型的bitmaps用来提供一组 Bitmap 的访问。


这样只有当第一次访问BitmapManager.bitmaps时,才会去加载 Bitmap。第二次访问时,也不会重新加载 Bitmap,可直接返回_bitmap


上面这段代码就是 Kotlin 预定义函数lazy()内部运用的技术,有了它就可以消灭模板代码:


class BitmapManager {
    val bitmaps by lazy { loadBitmaps() }
}


这里的关键词by出现在属性名后面,表示属性委托,即将属性的读和写委托给另一个对象,被委托的对象必须满足一定的条件:


  1. 对于 val 修饰的只读变量进行属性委托时,被委托的对象必须实现getValue()接口,即定义如何获取变量值。


  1. 对于 var 修饰的读写变量进行属性委托时,被委托对象必须实现getValue()setValue()接口,即定义如何读写变量值。


属性委托的三种实现方式


lazy()方法的返回值是一个Lazy对象:


public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
public interface Lazy<out T> {
    public val value: T
    public fun isInitialized(): Boolean
}


Lazy类并没有直接实现getValue()方法。它使用了另一种更加灵活的方式:


public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value


getValue()被声明为Lazy类的扩展函数。这是 Kotlin 独有的在类体外为类新增功能的特性。在原有类不能被修改的时候,特别好用。


除了扩展函数,还有另外两种方式可以实现被委托类(假设代理的类型为 String):


class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "Delegate"
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    }
}


这种方式新建了一个代理类,并且在类中通过关键词operator重载了getValue()setValue()这两个运算符,分别对应取值和设置操作。


最后一种方式如下(假设代理的类型为 String):


class Delegate : ReadWriteProperty<Any?, String> {
    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "Delegate"
    }
    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    }
}


即实现ReadWriteProperty接口中的getValue()setValue()方法。


然后就可以像这样使用代理类:


class Test {
    var str: String by Delegate()
}


属性委托背后的实现如下:


class Test {
    private delegate = Delegate()
    var str : String
        get () = delegate.getValue(this, kProperty)
        set (value: String) = delegate.setValue(this, kProperty, value)
}


新建的Delegate类会被存储到一个支持属性delegate中,委托属性的设置和取值方法的实现全权委托给代理类。


委托之后,当访问委托属性时就好比在调用代理类的方法:


val test = Text()
val str = test.str // 等价于 val str = test.delegate.getValue(test, kProperty)
val test.str = str // 等价于 test.delegate.setValue(test, Kproperty, str)


委托应用


1. 更简便地获取传参


委托可以隐藏细节,特别是当细节是一些模板代码的时候:


class TestFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val id = arguments?.getString("id") ?: ""
    }
}
class KotlinActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val id = intent?.getStringExtra("id") ?: ""
    }
}


获取传递给 Activity 或 Fragment 值的代码就很模板。可以使用委托隐藏一下细节:


// 新建 Extras 类作为被委托类
class Extras<out T>(private val key: String, private val default: T) {
    // 重载取值操作符
    operator fun getValue(thisRef: Any, kProperty: KProperty<*>): T? =
        when (thisRef) {
            // 获取传递给 Activity 的参数
            is Activity -> { thisRef.intent?.extras?.get(key) as? T ?: default }
            // 获取传递给 Fragment 的参数
            is Fragment -> { thisRef.arguments?.get(key) as? T ?: default }
            else -> default
        }
}


然后就可以像这样使用委托:


class TestActivity : AppCompatActivity() {
    private val id by Extras("id","0")
}
class TestFragment : Fragment() {
    private val id by Extras("id","0")
}


2. 更简便地获取 map 值


有些类的属性不是固定的,而是有时多,有时少,即动态的,比如:


class Person {
    private val attrs = hashMapOf<String, Any>()
    fun setAttrs( key: String, value: Any){
        attrs[key] = value
    }
    val name: String
        get() = attrs["name"]
}


有些Person有孩子,有些没有,所以不同Person实例拥有的属性集是不同的。这种场景用Map来存储属性就很合适。


上述代码可以用委托简化:


class Person {
    private val attrs = hashMapOf<String, Any>()
    fun setAttrs( key: String, value: Any){
        attrs[key] = value
    }
    val name: String by attrs
}


name的获取委托给一个 map 对象。神奇之处在于,甚至都不需要指定key就可以正确地从 map 中获取 name 属性值。这是因为 Kotlin 标准库已经为 Map 定义了getValue()setValue()扩展函数。属性名将自动作用于 map 的键。


总结


  1. Kotlin 委托分为类委托属性委托。它们都通过关键词by来进行委托。


  1. 类委托可以用简洁的语法将类的实现委托给另一个对象,以减少模板代码。


  1. 属性委托可以将对属性的访问委托给另一个对象,以减少模板代码并隐藏访问细节。


  1. 属性委托有三种实现方式,分别是扩展方法、实现ReadWriteProperty接口、重载运算符。


推荐阅读











目录
相关文章
|
15天前
|
存储 Kotlin
正则表达式在Kotlin中的应用:提取图片链接
正则表达式在Kotlin中的应用:提取图片链接
|
1月前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
46 4
|
6月前
|
存储 安全 Android开发
构建高效的Android应用:Kotlin与Jetpack的结合
【5月更文挑战第31天】 在移动开发的世界中,Android 平台因其开放性和广泛的用户基础而备受开发者青睐。随着技术的进步和用户需求的不断升级,开发一个高效、流畅且易于维护的 Android 应用变得愈发重要。本文将探讨如何通过结合现代编程语言 Kotlin 和 Android Jetpack 组件来提升 Android 应用的性能和可维护性。我们将深入分析 Kotlin 语言的优势,探索 Jetpack 组件的核心功能,并通过实例演示如何在实际项目中应用这些技术。
|
2月前
|
监控 安全 Java
Kotlin 在公司上网监控中的安卓开发应用
在数字化办公环境中,公司对员工上网行为的监控日益重要。Kotlin 作为一种基于 JVM 的编程语言,具备简洁、安全、高效的特性,已成为安卓开发的首选语言之一。通过网络请求拦截,Kotlin 可实现网址监控、访问时间记录等功能,满足公司上网监控需求。其简洁性有助于快速构建强大的监控应用,并便于后续维护与扩展。因此,Kotlin 在安卓上网监控应用开发中展现出广阔前景。
20 1
|
2月前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
76 1
|
3月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
61 4
|
3月前
|
XML Android开发 数据格式
Android实战经验之Kotlin中快速实现动态更改应用图标和名称
本文介绍在Android中通过设置多个活动别名动态更改应用图标和名称的方法,涉及XML配置及Kotlin代码示例。
158 10
|
3月前
|
设计模式 安全 编译器
Kotlin 中的密封类:详解与应用
【8月更文挑战第31天】
150 0
|
3月前
|
存储 前端开发 编译器
深入理解Kotlin中的数据类及其应用
【8月更文挑战第31天】
41 0
|
3月前
|
存储 Java Kotlin
Kotlin 布尔值教程:深入理解与应用示例
Kotlin中的布尔值是一种数据类型,仅能存储`true`或`false`两种状态,适用于表示二选一的情况,如开关或真假判断。布尔类型可通过`Boolean`关键字声明,并直接赋值为`true`或`false`。此外,Kotlin支持使用比较运算符创建布尔表达式,用于条件判断。条件语句包括`if`、`else`和`else if`,允许根据不同条件执行特定代码块。特别地,在Kotlin中,`if..else`结构不仅能作为语句使用,还能作为表达式,即可以在条件判断的同时返回一个值。这种灵活性使得Kotlin在处理条件逻辑时更为简洁高效。
46 1