Android进阶宝典—Kotlin委托

1、什么是委托?

委托,又叫委托模式是一种常用的设计模式,它可以让一个对象在不改变自己原有的行为的前提下,将某些特定的行为委托给另一个对象来实现。它通过将对象之间的关系分离,可以降低系统的耦合度,提高代码的复用性和可维护性。

其中有三个角色,约束、委托对象和被委托对象。

  • 约束: 一般为接口也可以是抽象类,定义了某个行为。
  • 被委托对象: 负责执行具体的行为。
  • 委托对象: 负责将约束中定义的行为交给被委托对象。

2、Java中的委托

先来说一说委托在Java中的应用用一个简单的例子来说明:

老板在创业初期时因为只有一个人而需要负责产品的客户端UI服务器。 这个时候老板负责的这些工作就可以被抽象出来形成一个约束接口:

public interface Work {
    void app();
    void ui();
    void service();
}
复制代码
public class Boss implements Work {

    @Override  
    public void app() {  
    System.out.println("Boss doing app");  
    }  

    @Override  
    public void ui() {  
    System.out.println("Boss doing ui");  
    }  

    @Override  
    public void service() {  
    System.out.println("Boss doing service");  
    }
}
复制代码

现在老板每天都在做这几件事:

public class Main {  
    public static void main(String[] args) {  
        Boss boss = new Boss();  
        boss.app();  
        boss.ui();  
        boss.service();  
    }  
}
复制代码

输出:

Boss doing app
Boss doing ui
Boss doing service
复制代码

运气不错,产品赚了不少钱,老板花钱雇了一个员工,将这些工作委托给他处理,自己直接脱产,只需要知道结果就可以了,于是就有了:

public class Employee implements Work{  
    @Override  
    public void app() {  
        System.out.println("Employee doing app");  
    }  

    @Override  
    public void ui() {  
        System.out.println("Employee doing ui");  
    }  

    @Override  
    public void service() {  
        System.out.println("Employee doing service");  
    }  
}
复制代码
public class Boss implements Work{  
    private Employee employee;  

    public Boss(Employee employee) {  
        this.employee = employee;  
    }  

    @Override  
    public void app() {  
        employee.app();  
    }  

    @Override  
    public void ui() {  
        employee.ui();  
    }  

    @Override  
    public void service() {  
        employee.service();  
    }  
}
复制代码
public class Main {  
    public static void main(String[] args) {  
        Boss boss = new Boss(new Employee());  
        boss.app();  
        boss.ui();  
        boss.service();  
    }  
}
Employee doing app
Employee doing ui
Employee doing service

这就是一个委托模式,老板委托对象)将 工作约束)委托给 员工被委托者)处理,老板并不关心每项工作具体是如何实现的,员工在完成工作后也会和老板汇报,就算这几项工作内容发生变化也只是员工需要处理。

3、Kotlin中的委托

那么针对上述的委托所描述例子在Kotlin中是如何实现的呢?

答案是使用关键字by,Kotlin专门推出了by来实现委托: 上述例子中的工作员工都不变:

interface Work {  
    fun app()  
    fun ui()  
    fun service()  
}
class Employee : Work {  
    override fun app() {  
    println("Employee doing app")  
    }  

    override fun ui() {  
    println("Employee doing ui")  
    }  

    override fun service() {  
    println("Employee doing service")  
    }  
}

老板这个类中,我们要将工作使用关键字by委托给员工

class Boss(private val employee: Employee) : Work by employee

就这么一行,实现了Java代码中老板类的效果。

fun main(args: Array<String>) {  
    val boss = Boss(Employee())  
    boss.app()  
    boss.ui()  
    boss.service()  
}

结果肯定是一样的。 那么by是如何实现Java中委托的效果的呢?通过反编译Kotlin字节码后我们看到:

public final class Boss implements Work {  
    private final Employee employee;  

    public Boss(@NotNull Employee employee) {  
        Intrinsics.checkNotNullParameter(employee, "employee");  
        super();  
        this.employee = employee;  
    }  

    public void app() {  
        this.employee.app();  
    }  

    public void service() {  
        this.employee.service();  
    }  

    public void ui() {  
        this.employee.ui();  
    }  
}

其实就是Java中实现委托的代码,Kotlin将它包成一个关键字by,效率大幅提升。

4、属性委托

上述说明的委托都属于类委托,而在Kotlin当中by不仅可以实现类委托,还可以实现属性委托,属性委托为Kotlin的一大特性,将对属性的访问委托给另一个对象。使用属性委托可以让我们编写更简洁、更模块化的代码,并且能够提高代码的可重用性。

4.1 如何实现属性委托?

使用方式:val/var <属性名>: <类型> by <表达式>

by 后面的表达式是该 委托, 属性对应的 get()set()会被委托给它的 getValue()setValue() 方法。 如果该属性是只读的(val)其委托只需要提供一个 getValue() 函数如果该属性是var则还需要提供 setValue()函数。例如:

   class Example {  
    var str: String by Delegate()  
   }
    class Delegate {  
        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {  
            return "$thisRef, thank you for delegating '${property.name}' to me!"  
        }  

        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {  
            println("$value has been assigned to '${property.name}' in $thisRef.")  
        }  
    }
fun main(args: Array<String>) {  
   val p = Example()  
   p.str = "Hello"  
   println(p.str) 
}

因为属性str是可变的所以在Delegate类中实现了getValue和setValue两个函数,其中一共出现了三个参数分别是

  • thisRef :读出 str 的对象
  • property :保存了对 str 自身的描述 (例如你可以取它的名字)
  • value :保存将要被赋予的值

运行结果如下:

Hello has been assigned to 'str' in Example@1ddc4ec2.
Example@1ddc4ec2, thank you for delegating 'str' to me!

我们再将Example类中的代码转为Kotlin字节码反编译得到以下代码:

 public final class Example {  
    // $FF: synthetic field  
    static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1(new MutablePropertyReference1Impl(Example.class, "str", "getStr()Ljava/lang/String;", 0))};  
    @NotNull  
    private final Delegate str$delegate = new Delegate();  

    @NotNull  
    public final String getStr() {  
        return this.str$delegate.getValue(this, $$delegatedProperties[0]);  
    }  

    public final void setStr(@NotNull String var1) {  
        Intrinsics.checkNotNullParameter(var1, "");  
        this.str$delegate.setValue(this, $$delegatedProperties[0], var1);  
    }  
}

就是创建了一个Delegate对象,再通过调用setVaule和getValue一对方法来获取和设置值的。

4.2 标准委托

在Kotlin标准库为委托提供了几种方法

4.2.1 延迟属性 Lazy

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

首次访问属性时才进行初始化操作,lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托, 该lambda表达式将在第一次访问该属性时被调用,初始化属性并返回属性值,之后的访问将直接返回初始化后的值。

简单的例子:

fun main(args: Array<String>) {  
    val str : String by lazy {  
        println("Hello str")  
        "lazy"  
    }  
    println(str)  
    println(str)  
}

输出:

Hello str//只在第一次访问时执行
//后续访问只返回值
lazy
lazy

当我们使用 by lazy 委托实现延迟初始化时,Kotlin 编译器会生成一个私有的内部类,用于实现委托属性的懒加载逻辑,其内部包含一个名为 value 的属性,用于存储真正的属性值。同时,还会生成一个名为 isInitialized 的私有 Boolean 属性,用于标识属性是否已经初始化。

当我们首次访问被 lazy 修饰的属性时,如果它还未被初始化,就会调用 lazy 所接收的 lambda 表达式进行初始化,并将结果保存在 value 属性中。之后,每次访问该属性时,都会返回 value 中存储的属性值。

4.2.2 可观察属性 Observable

Delegates.observable() 接受两个参数:初始值与修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值_后_执行)。它有三个参数:被赋值的属性、旧值与新值:

class User {  
    var name : String by Delegates.observable("no value") {  
        property, oldValue, newValue ->  
        println("property :${property.name}, old value $oldValue -> new value $newValue")  
    }  
}
fun main() {
    val user = User()
    user.name = "Alex"
    user.name = "Bob"
}

property :name, old value no value -> new value Alex
property :name, old value Alex -> new value Bob

如果你想截获赋值并“否决”它们,那么使用 vetoable() 取代 observable()。 在属性被赋新值生效_之前_会调用传递给 vetoable 的处理程序,简单来说就是利用你设定的条件来决定设定的值是否生效,还是以上述代码为例,在User中增加一个年龄属性:

var age : Int by Delegates.vetoable(0) {  
    _, oldValue, newValue ->  
    println("old value : $oldValue, new value : $newValue")  
    newValue > oldValue  
}

在这里我们设定了输入的年龄大于现在的年龄才生效,运行一下看看输出什么: 0 old value : 0, new value : 20 20 old value : 20, new value : 19 20 old value : 20, new value : 25 25

0
old value : 0, new value : 20
20
old value : 20, new value : 19
20
old value : 20, new value : 25
25

4.2.3 将属性储存在映射中

映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他“动态”事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。

class User(map: MutableMap) {  
    val name: String by map  
    val age: Int by map  
}

fun main(args: Array<String>) {  
    val user = User(  
    mutableMapOf(  
        "name" to "Alex",  
        "age" to 18  
        )  
    )  
    println("name : ${user.name}, age : ${user.age}")  
}

输出:

name : Alex, age : 18

5、总结

委托是一种常见的软件设计模式,旨在提高代码的复用性和可维护性,在 Java 中,委托通过定义接口和实现类来实现。实现类持有接口的实例,并将接口的方法委托给实例来实现。这种方式可以实现代码的复用和解耦,但是需要手动实现接口中的方法,比较繁琐,而在 Kotlin 中,委托通过by关键字实现委托其中还包括了属性委托一大特性,Kotlin 提供了很多内置的属性委托,比如延迟属性、映射属性等。此外,Kotlin 还支持自定义属性委托。自定义属性委托需要实现 getValuesetValue 方法,用于获取和设置属性的值,与 Java 的委托相比,Kotlin 的属性委托更加方便和简洁,减少样板代码。

如有需要Android进阶学习资料 请点击免费领取

你可能感兴趣的:(android,kotlin)