Kotlin:代理真的很簡單啊!

小小小小小粽子-發表於2019-04-03

我們知道在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()
    }
複製程式碼

好了,關於類代理就探究這麼多了。我們大多都知道組合優於繼承,其實代理也是繼承很好的替代方案,而且很靈活。通過本次學習大家應該都明白了類代理的使用方法,下一次,我們一起來扒一扒代理屬性。

相關文章