我們知道在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
複製程式碼
沒錯,只要用by
把SuperCalculator
類改成這樣就行了!不用再實現介面方法再呼叫代理物件對應的方法了,老規矩我們來看看位元組碼:
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()
}
複製程式碼
像我們這裡,我們一開始就知道對於SuperCalculator
跟QuantumCalculator
這些類,我們不會傳入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()
}
複製程式碼
好了,關於類代理就探究這麼多了。我們大多都知道組合優於繼承,其實代理也是繼承很好的替代方案,而且很靈活。通過本次學習大家應該都明白了類代理的使用方法,下一次,我們一起來扒一扒代理屬性。