Kotlin:代理真的很简单啊

我们知道在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()
    }

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341

推荐阅读更多精彩内容