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 怎么完成特点托付?

Kotlin官方文档中给出了界说:

运用办法: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, "<set-?>");  
        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 <T>实例的函数,回来的实例能够作为完成推迟特点的托付, 该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  
}

在这里咱们设定了输入的年纪大于现在的年纪才收效,运转一下看看输出什么:
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<String, Any?>) {
    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 的特点托付愈加便利和简练,削减样板代码。

6、感谢

  1. 校稿:ChatGpt
  2. 文笔优化:ChatGpt

参阅:Kotlin官方文档:托付 ,Kotlin官方文档:特点托付