The Kotlin proxy pattern is that simple

We are all familiar with the proxy pattern, which is to delegate what you want to do to another object, this object is the proxy object, Java proxy pattern includes static proxy and dynamic proxy, here is no more details, today we will talk about Kotlin’s proxy.

It’s natively supported with zero boilerplate code in Kotlin, which includes class proxies and property proxies

Class representative

interface Base { fun print() } class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } class Derived(b: Base) : Base by b fun main(args: Array<String>) {val b = BaseImpl(10) Derived(b).print()Copy the code

As you can see, Kotlin’s implementation of class proxying is very simple. With the keyword BY, YOU can have B proxying its own methods. B will be stored internally in Derived, and the compiler will generate all of the Base methods that are forwarded to B.

The property broker

When it comes to property delegate, lazy, Delegates. NotNull (), Delegates. Observable (), which are kotlin’s own standard delegate, we will write a property delegate ourselves

class Example {
	val p: String by Delegate()
}

class Delegate {
}
Copy the code

Miss ‘getValue(mainActivity: mainActivity, property: KProperty<*>): Any’ method on delegate of type ‘delegate’ means we need to provide a getValue method. If we change p to var, the compiler will say we need a setValue method.

class Example { var p: String by Delegate() } class Delegate { private var mRealValue = "" operator fun getValue(thisRef: Any? , property: KProperty<*>): String { return mRealValue } operator fun setValue(thisRef: Any? , property: KProperty<*>, value: String) { mRealValue = value } }Copy the code

A property’s proxy is the getValue() and setValue() methods to which get() and set() corresponding to its own property are delegated. Both functions need to be marked with the operator keyword, and the proxy class can implement one of the ReadOnlyProperty or ReadWriteProperty interfaces that contain the required operator methods, as declared in the Kotlin library.

Behind the implementation of each proxy property, the Kotlin compiler generates and proxies secondary properties to it. For example, for the property prop, generate the hidden property prop$delegate, and the accessor’s code simply delegates to this additional property:

class C { var prop: Class C {private val prop$delegate = MyDelegate() var prop: class C {private val prop$delegate = MyDelegate() var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) }Copy the code

The Kotlin compiler provides all the necessary information about prop in arguments: the first argument, this, refers to an instance of the external class C and this:: Prop is a reflection of type KProperty, which describes the prop itself. Let’s continue with the lazy method and write a lazy property of your own

class Demo1 {
	private val a by lazy { 1 }
}
Copy the code

Use the Kotlin Bytecode tool to view the bytecode and decompile it

public final class Demo1 { // $FF: synthetic field static final KProperty[] ? delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Reflection.getOrCreateKotlinClass(Demo1.class), "a", "getA()I"))}; private final Lazy a$delegate; private final int getA() { Lazy var1 = this.a$delegate; KProperty var3 = ? delegatedProperties[0]; return ((Number)var1.getValue()).intValue(); } public Demo1() { this.a$delegate = LazyKt.lazy((Function0)null.INSTANCE); }}Copy the code

The compiler generates a Lazy $delegate for us, as well as? DelegatedProperties’ KProperty[] object, has a getA method, and initializes a$delegate in the construct via lazykt.lazy ((Function0) null.instance), This method is actually our lazy() method, so let’s move on to the lazy method

public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer) private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable { private var initializer: (() -> T)? = initializer @Volatile private var _value: Any? = UNINITIALIZED_VALUE // final field is required to enable safe publication of constructed instance private val lock = lock ? : this override val value: T get() { val _v1 = _value if (_v1 ! == UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") return _v1 as T } return synchronized(lock) { val _v2 = _value if (_v2 ! == UNINITIALIZED_VALUE) { @Suppress("UNCHECKED_CAST") (_v2 as T) } else { val typedValue = initializer!! () _value = typedValue initializer = null typedValue } } } override fun isInitialized(): Boolean = _value ! == UNINITIALIZED_VALUE override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet." private fun writeReplace(): Any = InitializedLazyImpl(value) }Copy the code

In the SynchronizedLazyImpl implementation, _value is used to actually hold the value of the property. The default value for _value is UNINITIALIZED_VALUE (a custom object). When _value is not the default value, the value of _value is returned directly as getValue(); When _value is still the default value, the initializer initialization expression is called to complete the initialization, assign the value to _value and return it as getValue()

So when we get the value of a, we call getA(), call Lazy getValue, and then follow the Lazy logic of the SynchronizedLazyImpl.