偷师 - Kotlin 委托
关键字
synchorinzed
CAS
委托/代理模式
委托
要理解 kotlin-委托
的作用和用法首先要理解什么是委托。初看委托二字如果不太理解的话不妨转换成代理二字。委托模式和代理模式是一种设计模式的两种称呼而已。
委托/代理模式
代理模式,字面理解就是自己不方便做或者不能做的事情,需要第三方代替来做,最终通过第三方来达到自己想要的目的或效果。举例:员工小李在B总公司打工,B总成天让小李加班不给加班费,小李忍受不住了,就想去法院告B总。虽然法律上允许打官司不请律师,允许自辩。但是小李第一不熟悉法律起诉的具体流程,第二嘴比较笨,人一多腿就抖得厉害。因此,小李决定去找律师帮忙打官司。找律师打官司和自己打官司相比,有相同的地方,也有不同的地方。
相同的地方在于:
- 都需要提交原告的资料,如姓名、年龄、事情缘由、想达到的目的。
- 都需要经过法院的取证调查,开庭争辩等过程。
- 最后拿到审判结果。
不同地方在于:
- 小李省事了,让专业的人做专业的事,不需要自己再去了解法院那一套繁琐复杂的流程。
- 把握更大了。
通过上面的例子,我们注意到代理模式有几个重点。
- 被代理的角色(小李)
- 代理角色(律师)
- 协议(不管是代理和被代理谁去做,都需要做的事情,抽象出来就是协议)
UML
类图:
代码实现如下:
//协议
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
函数会根据所选模式的不同返回不同的实例对象:SynchronizedLazyImpl
、SafePublicationLazyImpl
、UnsafeLazyImpl
。这三者之间最大的的区别在于 getter()
函数的实现,但不管如何最终都是各自类中的 value
属性代理 lazy
函数所修饰的属性。
synchorinzed
、CAS
都是多线程中实现锁的常用烦恼干是,关于他们的介绍可以看我之前的文章:
应用
在项目中可以应用 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
}
}
}
复制代码
使用过后代码非常的简洁而且也不需要再用 !!
或者定义一个新的变量,有兴趣的同学可以去看下源码。