Kotlin:代理真的很简单啊!

1,545 阅读4分钟

我们知道在Kotlin里语法糖的存在都是为了解决之前Java某种现有的问题或者是简化代码,之前我们已经讨论了诸多语法糖,了解它们的实现以及如何优化。在我们常用的第三方库中,一个比较常见的东西就是代理模式,但是这个东西写起来略繁琐,好在到了Kotlin这里,在语言层面上支持代理,我们一起来了解下。

首先,什么是代理,代理就是代为处理的意思,我们把要做的事情委托给万事屋,由万事屋来帮我们完成,日常我们请保洁,找代驾,都可以说她们是我们的代理。代理也分为类代理跟代理属性,今天我们主要讨论的是类代理。

为了让大家有个直观的理解,我们来看个小示例:

interface Calculator {
    fun calculate():Int
}

然后我们让两个类来实现这个接口:

class CalculatorBrain:Calculator {
    override fun calculate(): Int {
        TODO("not implemented")
    }
}

class SuperCalculator(val calculator: Calculator) : Calculator {
    override fun calculate(): Int {
        return calculator.calculate()
    }
}

在这里,两个类实现了接口并实现了自己的calculate方法,只不过SuperCalculator的实现是调用了其它方法。

最后我们就可以这样用啦:

class Main {
    fun main(args: Array<String>) {
        val calculatorBrain = CalculatorBrain()
        val superCalculator = SuperCalculator(calculatorBrain)
        superCalculator.calculate()
    }
}

在上面例子中,calculatorBrain就是superCaculator的代理,任何时候我们让superCalculator做事它都会转发给calculatorBrain来做,事实上,任何实现了Calculator接口的类都可以传给SuperCalculator,这种把代理对象显式传给其他对象的方式,我们就称之为显式代理

显式代理任何面向对象的语言都能实现,而且每次都需要我们实现相同的接口,传入代理对象,然后在重写的方法中调用代理对象的方法,感觉又是一大波重复劳动,而Kotlin在语法层面上使用了by关键字给我们提供了支持,语言级支持的代理就是隐式代理

我们来看看我们上面的例子使用Kotlin的特性改写是什么样的:

class SuperCalculator(val calculator: Calculator) : Calculator by calculator

没错,只要用bySuperCalculator类改成这样就行了!不用再实现接口方法再调用代理对象对应的方法了,老规矩我们来看看字节码:

public final class SuperCalculator implements Calculator {


  // access flags 0x12
  private final LCalculator; calculator
  @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x11
  public final getCalculator()LCalculator;
  @Lorg/jetbrains/annotations/NotNull;() // invisible
   L0
    LINENUMBER 1 L0
    ALOAD 0
    GETFIELD SuperCalculator.calculator : LCalculator;
    ARETURN
   L1
    LOCALVARIABLE this LSuperCalculator; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  public <init>(LCalculator;)V
    // annotable parameter count: 1 (visible)
    // annotable parameter count: 1 (invisible)
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 1
    LDC "calculator"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 1 L1
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ALOAD 0
    ALOAD 1
    PUTFIELD SuperCalculator.calculator : LCalculator;
    RETURN
   L2
    LOCALVARIABLE this LSuperCalculator; L0 L2 0
    LOCALVARIABLE calculator LCalculator; L0 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  public calculate()I
   L0
    ALOAD 0
    GETFIELD SuperCalculator.calculator : LCalculator;
    INVOKEINTERFACE Calculator.calculate ()I (itf)
    IRETURN
   L1
    LOCALVARIABLE this LSuperCalculator; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1
}


咳咳,换汤不换药,跟我们自己用Java实现代理大体相似,只不过减少了我们的重复劳动,编译器为我们做了实现。可能有些同学看不太明白,我们直接反编译成Java代码看看:

public final class SuperCalculator implements Calculator {
   @NotNull
   private final Calculator calculator;

   @NotNull
   public final Calculator getCalculator() {
      return this.calculator;
   }

   public SuperCalculator(@NotNull Calculator calculator) {
      Intrinsics.checkParameterIsNotNull(calculator, "calculator");
      super();
      this.calculator = calculator;
   }

   public int calculate() {
      return this.calculator.calculate();
   }
}

这样就比较清楚了,注意生成的字节码只有get方法没有set方法,因为我在构造器里把calculator声明成val,所以反编译的代码里面也使用final修饰了它。显式代理由于代理对象都是我们自己传入的我们可以自己添加set方法来实现随意替换的手段,那这里我把val改成var,能不能动态替换代理对象呢?我们来试一下:

class SuperCalculator(var calculator: Calculator) : Calculator by calculator

直接来看反编译后的Java代码:

public final class SuperCalculator implements Calculator {
   @NotNull
   private Calculator calculator;
   // $FF: synthetic field
   private final Calculator ?delegate_0;

   @NotNull
   public final Calculator getCalculator() {
      return this.calculator;
   }

   public final void setCalculator(@NotNull Calculator var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.calculator = var1;
   }

   public SuperCalculator(@NotNull Calculator calculator) {
      Intrinsics.checkParameterIsNotNull(calculator, "calculator");
      super();
      this.?delegate_0 = calculator;
      this.calculator = calculator;
   }

   public int calculate() {
      return this.?delegate_0.calculate();
   }
}

我们看到确实多了一个set方法,但是生成的类里面却有两个Calculator对象,查看生成的calculate方法也可以看到实际起作用的是?delegate_0,而调用set方法的时候并不会给它赋值,它使用final来声明。这意味着Kotlin的代理是不支持在运行时动态替换的,这跟我们自己实现显式代理有些区别。也建议大家在用kotlin写代理时,构造器里使用val,避免编译器给我们生成无谓的字段跟方法,减少开销。

假设我们这时候又多了一个新型的量子计算器:

class QuantumCalculator(val calculator: Calculator) : Calculator by calculator

我们也可以这样使用它了:

fun main(args: Array<String>) {
        val calculatorBrain = CalculatorBrain()
        val superCalculator = SuperCalculator(calculatorBrain)
        superCalculator.calculate()
        val calculatorBrain1 = CalculatorBrain()
        val quantumCalculator = QuantumCalculator(calculatorBrain1)
        quantumCalculator.calculate()
    }

在这里我们每次创建一个新的Calculator对象,就会新创建一个CalculatorBrain对象,但是在这个场景下,传入的代理对象行为都是一样的,都是CalculatorBrain,为了节约宝贵的内存,我们可以复用一个已有的CalculatorBrain,在创建其他对象需要时使用它即可:

fun main(args: Array<String>) {
        val calculatorBrain = CalculatorBrain()
        val superCalculator = SuperCalculator(calculatorBrain)
        superCalculator.calculate()
        val quantumCalculator = QuantumCalculator(calculatorBrain)
        quantumCalculator.calculate()
    }

像我们这里,我们一开始就知道对于SuperCalculatorQuantumCalculator这些类,我们不会传入CalculatorBrain外的其他对象作为代理,那每次使用时还要再传入代理对象,而且是相同的代理对象,也很繁琐,也算得上是样板代码,我们可以直接创建个CalculatorBrain的单例:

object CalculatorBrain: Calculator{
    override fun calculate(): Int {
        TODO("not implemented") 
    }
}

然后直接集成到这些类的声明里面:

class SuperCalculator() : Calculator by calculatorBrain

class QuantumCalculator() : Calculator by calculatorBrain

看,代码更加简洁明了了。

我们来看看反编译后Quantum的Java代码:

public final class QuantumCalculator implements Calculator {
   // $FF: synthetic field
   private final CalculatorBrain ?delegate_0;

   public QuantumCalculator() {
      this.?delegate_0 = CalculatorBrain.INSTANCE;
   }

   public int calculate() {
      return this.?delegate_0.calculate();
   }
}

我们可以看到,在默认构造函数里,我们给一个用final声明的代理赋值了,而值来自于CalculatorBrain.INSTANCE,从命名来看,这是一个单例,我们这就来看看它的代码:

public final class CalculatorBrain implements Calculator {
   public static final CalculatorBrain INSTANCE;

   public int calculate() {
      String var1 = "not implemented";
      throw (Throwable)(new NotImplementedError("An operation is not implemented: " + var1));
   }

   private CalculatorBrain() {
   }

   static {
      CalculatorBrain var0 = new CalculatorBrain();
      INSTANCE = var0;
   }
}

源码面前无秘密,这确实是一个单例。

现在,我们可以更简单地使用这些类了:

fun main(args: Array<String>) {
        SuperCalculator().calculate()
        QuantumCalculator().calculate()
    }

好了,关于类代理就探究这么多了。我们大多都知道组合优于继承,其实代理也是继承很好的替代方案,而且很灵活。通过本次学习大家应该都明白了类代理的使用方法,下一次,我们一起来扒一扒代理属性。