注册

偷师 - Kotlin 委托

关键字

  • synchorinzed
  • CAS
  • 委托/代理模式

委托

要理解 kotlin-委托 的作用和用法首先要理解什么是委托。初看委托二字如果不太理解的话不妨转换成代理二字。委托模式和代理模式是一种设计模式的两种称呼而已。

委托/代理模式

代理模式,字面理解就是自己不方便做或者不能做的事情,需要第三方代替来做,最终通过第三方来达到自己想要的目的或效果。举例:员工小李在B总公司打工,B总成天让小李加班不给加班费,小李忍受不住了,就想去法院告B总。虽然法律上允许打官司不请律师,允许自辩。但是小李第一不熟悉法律起诉的具体流程,第二嘴比较笨,人一多腿就抖得厉害。因此,小李决定去找律师帮忙打官司。找律师打官司和自己打官司相比,有相同的地方,也有不同的地方。

相同的地方在于:

  • 都需要提交原告的资料,如姓名、年龄、事情缘由、想达到的目的。
  • 都需要经过法院的取证调查,开庭争辩等过程。
  • 最后拿到审判结果。

不同地方在于:

  • 小李省事了,让专业的人做专业的事,不需要自己再去了解法院那一套繁琐复杂的流程。
  • 把握更大了。

通过上面的例子,我们注意到代理模式有几个重点。

  • 被代理的角色(小李)
  • 代理角色(律师)
  • 协议(不管是代理和被代理谁去做,都需要做的事情,抽象出来就是协议)

UML 类图: image

代码实现如下:

//协议
interface Protocol{
//登记资料
public void register(String name);
//调查案情,打官司
public void dosomething();
//官司完成,通知雇主
public void notifys();
}

//代理角色:律师类
class LawyerProxy implements Protocol{
private Employer employer;
public LawyerProxy(Employer employer){
this.employer=employer;
}
@Override
public void register(String name) {
// TODO Auto-generated method stub
this.employer.register(name);
}
public void collectInfo(){
System.out.println("作为律师,我需要根据雇主提供的资料,整理与调查,给法院写出书面文字,并提供证据。");
}
@Override
public void dosomething() {
// TODO Auto-generated method stub
collectInfo();
this.employer.dosomething();
finish();
}
public void finish(){
System.out.println("本次官司打完了...............");
}
@Override
public void notifys() {
// TODO Auto-generated method stub
this.employer.notifys();
}
}

//被代理角色:雇主类
class Employer implements Protocol{
String name=null;
@Override
public void register(String name) {
// TODO Auto-generated method stub
this.name=name;
}
@Override
public void dosomething() {
// TODO Auto-generated method stub
System.out.println("我是'"+this.name+"'要告B总,他每天让我不停的加班,还没有加班费。");
}
@Override
public void notifys() {
// TODO Auto-generated method stub
System.out.println("法院裁定,官司赢了,B总需要赔偿10万元精神补偿费。");
}
}

public class Client {
public static void main(String[] args) {
Employer employer=new Employer();
System.out.println("我受不了了,我要打官司告老板");
System.out.println("找律师解决一下吧......");
Protocol lawyerProxy=new LawyerProxy(employer);
lawyerProxy.register("朵朵花开");
lawyerProxy.dosomething();
lawyerProxy.notifys();
}
}
复制代码

运行后,打印如下:

我受不了了,我要打官司告老板
找律师解决一下吧......
作为律师,我需要根据雇主提供的资料,整理与调查,给法院写出书面文字,并提供证据。
我是'朵朵花开'要告B总,他每天让我不停的加班,还没有加班费。
本次官司打完了...............
法院裁定,官司赢了,B总需要赔偿10万元精神补偿费。
复制代码

类委托

对代理模式有了一些了解之后我们再来看 kotlin-类委托 是如何实现的:

interface Base {
fun print()
}

class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main() {
val b = BaseImpl(10)
Derived(b).print()
}
复制代码

这是Kotlin 语言中文站的示例,转成 Javaa 代码如下:


public interface Base {
void print();
}

// BaseImpl.java
public final class BaseImpl implements Base {
private final int x;

public void print() {
int var1 = this.x;
boolean var2 = false;
System.out.print(var1);
}

public final int getX() {
return this.x;
}

public BaseImpl(int x) {
this.x = x;
}
}

// Derived.java
public final class Derived implements Base {
// $FF: synthetic field
private final Base $$delegate_0;

public Derived(@NotNull Base b) {
Intrinsics.checkNotNullParameter(b, "b");
super();
this.$$delegate_0 = b;
}

public void print() {
this.$$delegate_0.print();
}
}

// DelegateTestKt.java
public final class DelegateTestKt {
public static final void main() {
BaseImpl b = new BaseImpl(10);
(new Derived((Base)b)).print();
}

// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
复制代码

可以看到在 Derived 中已经实现了 Base 接口的抽象方法,而且方法的实际调用者是构造对象时传入的 Base 实例对象,也就是 BaseImpl 的实例对象。

对比上文介绍的代理模式:

  • Base:代理协议
  • BaseImpl:代理角色
  • Derived:被代理被代理角色

这样看的话,d上文类委托示例的结果包括重写方法实现和成员变量产生的结果的原因也就清晰明了了。

属性委托

kotlin 标准库中提供的属性委托有:

  • lazy:延迟属性;
  • Delegates.notNull():不能为空;
  • Delegates.observable():可观察属性;
  • Delegates.vetoable():可观察属性,可拒绝修改属性;

lazy 延迟属性下面再来分析,先来看 Delegates 的几个方法。

在 Delegate.kt 文件中定义了提供的标准属性委托方法,代码量很少就不贴代码了。可以看到三种委托方法都返回 ReadWriteProperty 接口的实例对象,它们的顶层接口是 ReadOnlyProperty 接口。名字就很提现它们各自的功用了:

  • ReadOnlyProperty:仅用于可读属性,val
  • ReadWriteProperty:用于可读-写属性,var

在属性委托的实现里,对应代理模式的角色如下:

  • 协议:ReadOnlyProperty 和 ReadWriteProperty
  • 代理者:Delegate
  • 被代理者:实际使用属性。

Delegates.notNull() 比较简单,拿它来分析下属性委托是如何实现的。

private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null

public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
}

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
}
复制代码
class DelegateTest {
private val name: String by Delegates.notNull()
}
复制代码

kotlin 转 Java

public final class DelegateTest {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(DelegateTest.class, "name", "getName()Ljava/lang/String;", 0))};
private final ReadWriteProperty name$delegate;

private final String getName() {
return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
}

public DelegateTest() {
this.name$delegate = Delegates.INSTANCE.notNull();
}
}
复制代码

可以看到 name 属性委托给了 NotNullVar 的 value 属性。当访问 name 属性时,其实访问的是 NotNullVar 的 value 属性。

自定义委托

上文提到 Delegates 中的委托方法都返回 ReadWriteProperty 接口的实例对象。如果需要自定义委托的话当然也是通过实现 ReadWriteProperty 接口了。

  • var 属性自定义委托:继承 ReadWriteProperty 接口,并实现 getValue()、setValue() 方法;
  • val 属性自定义委托:实现 ReadOnlyProperty 接口,并实现 getValue 方法。
public override operator fun getValue(thisRef: T, property: KProperty<*>): V

public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
复制代码

参数如下:

  • thisRef —— 必须与属性所有者类型相同或者是其超类型,通俗说就是属性所在类的类型或其父类型;
  • property —— 必须是 KProperty<*> 类型或其超类型。

Lazy

lazy 放到这里来分析是因为它虽然也是将属性委托给了其他类的属性,但它并没有继承 ReadWriteProperty 或 ReadOnlyProperty 接口并不是标准的属性委托。

lazy 源码如下:

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
复制代码

lazy 函数接收两个参数:

  • LazyThreadSafetyMode:线程安全模式;
  • initializer:初始化函数。

LazyThreadSafetyMode:不同模式的作用如下:

  • SYNCHRONIZED:通过 Volatile + synchorinzed 锁的方式保证在多线程情况下初始化函数仅调用一次,变量仅赋值一次;
  • PUBLICATION:通过 Volatile + CAS 的方式保证在多线程情况下变量仅赋值一次;
  • NONE:线程不安全。

lazy 函数返回 Lazy 接口实例。

注意:除非你能保证 lazy 实例的永远不会在多个线程初始化,否则不应该使用 NONE 模式。

lazy 函数会根据所选模式的不同返回不同的实例对象:SynchronizedLazyImplSafePublicationLazyImplUnsafeLazyImpl。这三者之间最大的的区别在于 getter() 函数的实现,但不管如何最终都是各自类中的 value 属性代理 lazy 函数所修饰的属性。

synchorinzedCAS 都是多线程中实现锁的常用烦恼干是,关于他们的介绍可以看我之前的文章:

应用

在项目中可以应用 kotlin 委托 可以辅助简写如下功能:

  • Fragment / Activity 传参
  • ViewBinding

本节所写的两个示例是摘自

Kotlin | 委托机制 & 原理 & 应用 -- 彭丑丑 View Binding 与Kotlin委托属性的巧妙结合,告别垃圾代码! -- Kirill Rozov 著,依然范特稀西 译

kotlin 委托 + Fragment / Activity 传参

示例来源: 彭丑丑 - Kotlin | 委托机制 & 原理 & 应用 项目地址: Github - DemoHall

属性委托前:

class OrderDetailFragment : Fragment(R.layout.fragment_order_detail) {

private var orderId: Int? = null
private var orderType: Int? = null

companion object {

const val EXTRA_ORDER_ID = "orderId"
const val EXTRA_ORDER_TYPE = "orderType";

fun newInstance(orderId: Int, orderType: Int?) = OrderDetailFragment().apply {
Bundle().apply {
putInt(EXTRA_ORDER_ID, orderId)
if (null != orderType) {
putInt(EXTRA_ORDER_TYPE, orderType)
}
}.also {
arguments = it
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

arguments?.let {
orderId = it.getInt(EXTRA_ORDER_ID, 10000)
orderType = it.getInt(EXTRA_ORDER_TYPE, 2)
}
}
}
复制代码

定义 ArgumentDelegate.kt

fun <T> fragmentArgument() = FragmentArgumentProperty<T>()

class FragmentArgumentProperty<T> : ReadWriteProperty<Fragment, T> {

override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
return thisRef.arguments?.getValue(property.name) as? T
?: throw IllegalStateException("Property ${property.name} could not be read")
}

override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
val arguments = thisRef.arguments ?: Bundle().also { thisRef.arguments = it }
if (arguments.containsKey(property.name)) {
// The Value is not expected to be modified
return
}
arguments[property.name] = value
}
}
复制代码

使用属性委托后:

class OrderDetailFragment : Fragment(R.layout.fragment_order_detail) {

private lateinit var tvDisplay: TextView

private var orderId: Int by fragmentArgument()
private var orderType: Int? by fragmentArgumentNullable(2)

companion object {
fun newInstance(orderId: Int, orderType: Int?) = OrderDetailFragment().apply {
this.orderId = orderId
this.orderType = orderType
}
}

override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
// Try to modify (UnExcepted)
this.orderType = 3
// Display Value
tvDisplay = root.findViewById(R.id.tv_display)
tvDisplay.text = "orderId = $orderId, orderType = $orderType"
}
}
复制代码

kotlin 委托 + ViewBinding

示例来源: ViewBindingPropertyDelegate

属性委托前:

class ProfileActivity : AppCompatActivity(R.layout.activity_profile) {

private var binding: ActivityProfileBinding? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityProfileBinding.inflate(layoutInflater)
binding!!.profileFragmentContainer
}
}
复制代码

属性委托后:

class ProfileActivity : AppCompatActivity(R.layout.activity_profile) {

private val viewBinding: ActivityProfileBinding by viewBinding()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
with(viewBinding) {
profileFragmentContainer
}
}
}
复制代码

使用过后代码非常的简洁而且也不需要再用 !! 或者定义一个新的变量,有兴趣的同学可以去看下源码。

0 个评论

要回复文章请先登录注册