一、首先,委托和代理算不上一个东西
代理模式和委托模式类似,但是两者不能认为是同一种模式。
- 委托模式 Delegation pattern
- 代理模式 Proxy Pattern
Kotlin 使用 by 关键字来实现委托(delegat) 模式
虽然委托和代理算不上一个东西,但是实际上很多时候,大多数人都把他说成本一个东西。
二、关于by
阅读编写kotlin代码的时候,by几乎无处不在。
by的两种用途
在kotlin中,by关键字主要有3种用途,
- 类委托: 一个类的方法不在该类中定义,而是直接委托给另一个对象来处理。
- 属性委托: 一个类的属性不在该类中定义,而是直接委托给另一个对象来处理。
- 局部变量委托: 一个局部变量不在该方法中定义,而是直接委托给另一个对象来处理。
by关键字后面的表达式可能有各种各样的写法,但一定是返回一个委托的实例
.
.
by这个语法糖帮我们干了什么事
Kotlin委托其实就是使用by语法后,编译器会帮我们生成了委托类的代码。
如果是接口的委托,by语法糖这样做
class RealSubject : Subject by Delegate()
编译器就会帮我们生成委托模式的代码:
class RealSubject : Subject {
private val delegate: Subject = Delegate()
override fun buy() {
delegate.buy()
}
}
.
.
如果是属性的委托,by语法糖这样做
var name: String by PropertyDelegate()
编译器就会帮我们把属性的get、set方法委托出去:
val delegate = PropertyDelegate()
var name: String
get() = delegate.getValue()
set(value) = delegate.setValue(value)
by关键字后面的表达式可能有各种各样的写法,但一定是返回一个委托的实例。
.
.
二、 属性委托
委托属性的语法
如下:
val/var <属性名>: <类型> by <表达式>
委托属性这个点比较简单,所以我们放在最开始的时候讲。
by lazy本身是一种属性委托。属性委托的关键字是by- lazy
只能修饰val,即不可变变量(仅仅说的是lazy) - kotlin中,
当且仅当变量被第一次调用的时候,委托方法才会执行。所以这也经常被用于kotlin的单例
属性代理是借助于代理设计模式,把这个模式应用于一个属性时,它可以将访问器的逻辑代理给一个辅助对象。
可以简单理解为属性的 setter、getter 访问器内部实现是交给一个代理对象来实现,相当于使用一个代理对象来替换了原来简单属性字段读写过程,而暴露外部属性操作还是不变的,照样是属性赋值和读取,只是 setter、getter 内部具体实现变了。
1.2 基本语法格式
class Student{
var name: String by Delegate()
}
class Delegate{
operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T{
...
}
operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T){
...
}
}
代码块
123456789101112
.
.
属性代理的理解/自定义属性代理
如果单纯用by,不是by lazy,相当于把属性的 setter、getter 访问器内部实现是交给一个代理对象来实现。
属性代理是借助于代理设计模式,把这个模式应用于一个属性时,它可以将访问器的逻辑代理给一个辅助对象。
可以简单理解为属性的 setter、getter 访问器内部实现是交给一个代理对象来实现,相当于使用一个代理对象来替换了原来简单属性字段读写过程,而暴露外部属性操作还是不变的,照样是属性赋值和读取,只是 setter、getter 内部具体实现变了。
class Student{
var name: String by Delegate()
}
class Delegate{
operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T{
...
}
operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T){
...
}
}
属性 name 将它访问器的逻辑委托给了 Delegate 对象,通过 by 关键字对表达式 Delegate() 求值获取这个对象。任何符合属性代理规则都可以使用 by 关键字。属性代理类必须要遵循 getValue(),setValue()方法约定,getValue、setValue方法可以是普通方法也可以是扩展方法,并且是方法是支持运算符重载。如果是 val 修饰的属性只需要具备 getValue() 方法即可。
属性代理基本流程就是代理类中的 getValue() 方法包含属性getter访问器的逻辑实现,setValue()方法包含了属性setter访问器的逻辑实现。当属性 name 执行赋值操作时,会触发属性 setter 访问器,然后在setter 访问器内部调用 delegate 对象的 setValue() 方法;执行读取属性 name 操作时,会在 getter 访问器中调用 delegate 对象的 getValue 方法.
.
.
by lazy一个例子
class Cat() {
// by lazy 当且仅当变量被第一次调用的时候,委托方法才会执行。
val name by lazy {
println("只调用一次")
"大脸猫"
}
}
fun main() {
val lazyCat = Cat()
// lazyCat.name的时候,委托方法执行
val name = lazyCat.name
// lazyCat1.name执行的时候,委托方法上面执行过了,不再执行了
val name1 = lazyCat.name
println("====")
println("lazy-name:$name")
println("lazy-name:$name1")
}
输出:
只调用一次
====
lazy-name:大脸猫
lazy-name:大脸猫
Kotlin 标准库中提供了几种委托
- 延迟属性(lazy properties): 其值只在首次访问时计算;
- 可观察属性(observable properties): 监听器会收到有关此属性变更的通知;
- 把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。
1.1 延迟属性 lazy
lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托:第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
val lazyProp: String by lazy {
println("Hello,第一次调用才会执行我!")
"西哥!"
}
// 打印lazyProp 3次,查看结果
fun main() {
println(lazyProp)
println(lazyProp)
println(lazyProp)
}
打印结果如下:
Hello,第一次调用才会执行我!
西哥!
西哥!
西哥!
可以看到,只有第一次调用,才会执行lambda表达式中的逻辑,后面调用只会返回lambda表达式的最终值。
1.1.1 lazy 也可以接受参数
lazy延迟初始化是可以接受参数的,提供了如下三个参数:
/**
* Specifies how a [Lazy] instance synchronizes initialization among multiple threads.
*/
public enum class LazyThreadSafetyMode {
/**
* Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
*/
SYNCHRONIZED,
/**
* Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
* but only the first returned value will be used as the value of [Lazy] instance.
*/
PUBLICATION,
/**
* No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
*
* This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
*/
NONE,
}
三个参数解释如下:
LazyThreadSafetyMode.SYNCHRONIZED: 添加同步锁,使lazy延迟初始化线程安全LazyThreadSafetyMode. PUBLICATION:初始化的lambda表达式可以在同一时间被多次调用,但是只有第一个返回的值作为初始化的值。LazyThreadSafetyMode. NONE:没有同步锁,多线程访问时候,初始化的值是未知的,非线程安全,一般情况下,不推荐使用这种方式,除非你能保证初始化和属性始终在同一个线程
使用如下:
val lazyProp: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("Hello,第一次调用才会执行我!")
"西哥!"
}
如果你指定的参数为LazyThreadSafetyMode.SYNCHRONIZED,则可以省略,因为lazy默认就是使用的LazyThreadSafetyMode.SYNCHRONIZED。
1.2 可观察属性 Observable
如果你要观察一个属性的变化过程,那么可以将属性委托给Delegates.observable, observable函数原型如下:
public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
ReadWriteProperty<Any?, T> =
object : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
}
接受2个参数:
initialValue:初始值onChange:属性值被修改时的回调处理器,回调有三个参数property,oldValue,newValue,分别为:被赋值的属性、旧值与新值。
使用如下:
var observableProp: String by Delegates.observable("默认值:xxx"){
property, oldValue, newValue ->
println("property: $property: $oldValue -> $newValue ")
}
// 测试
fun main() {
observableProp = "第一次修改值"
observableProp = "第二次修改值"
}
打印如下:
property: var observableProp: kotlin.String: 默认值:xxx -> 第一次修改值
property: var observableProp: kotlin.String: 第一次修改值 -> 第二次修改值
可以看到,每一次赋值,都能观察到值的变化过程。
1.2.1 vetoable 函数
vetoable 与 observable一样,可以观察属性值的变化,不同的是,vetoable可以通过处理器函数来决定属性值是否生效。
来看这样一个例子:声明一个Int类型的属性vetoableProp,如果新的值比旧值大,则生效,否则不生效。
代码如下:
var vetoableProp: Int by Delegates.vetoable(0){
_, oldValue, newValue ->
// 如果新的值大于旧值,则生效
newValue > oldValue
}
测试代码:
fun main() {
println("vetoableProp=$vetoableProp")
vetoableProp = 10
println("vetoableProp=$vetoableProp")
vetoableProp = 5
println("vetoableProp=$vetoableProp")
vetoableProp = 100
println("vetoableProp=$vetoableProp")
}
打印如下:
vetoableProp=0
0 -> 10
vetoableProp=10
10 -> 5
vetoableProp=10
10 -> 100
vetoableProp=100
可以看到10 -> 5 的赋值没有生效。
3.3 属性存储在映射中
还有一种情况,在一个映射(map)里存储属性的值,使用映射实例自身作为委托来实现委托属性,如:
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
测试如下:
fun main() {
val user = User(mapOf(
"name" to "西哥",
"age" to 25
))
println("name=${user.name} age=${user.age}")
}
打印如下:
name=西哥 age=25
使用映射实例自身作为委托来实现委托属性,可以使用在json解析中,因为json本身就可以解析成一个map。
三、 kotlin委托模式/类委托 (重点)
委托,也就是委托模式,它是23种经典设计模式种的一种,又名
代理模式,在委托模式中,有2个对象参与同一个请求的处理,接受请求的对象将请求委托给另一个对象来处理。
委托模式是一项技巧,其他的几种设计模式如:策略模式、状态模式和访问者模式都是委托模式的具体场景应用。
- 委托模式中,有三个角色,
约束、委托对象和被委托对象。- 举个例子,游戏代练公司。
- 游戏代练公司就是委托对象,代练就是被委托对象
- 委托对象(游戏公司) 让 被委托对象干活,具体干什么要看任务要求,也就是看约束
- 委托对象就是安排工作的人,具体怎么安排,要看约束的规定
- 但是,代练除了完成任务之外,平时自己还可以干点别的,人任务的前前后后随便玩
- 委托模式的本质就是,就是一个对象将消息委托给另一个对象来处理。
其实可以简单地理解为,所谓的
类委托,代理模式,代理接口,委托模式,就是我们后面要说的东西。
一句话说委托
委托模式的本质就是,在实现类中,用委托对象的方法 代替实现类中的方法,并适当增加一些自己的逻辑。
Kotlin 类委托的语法格式如下:
class <类名>(b : <基础接口>) : <基础接口> by <基础对象>
比如:class DelegateGamePlayer(private val iGamePlayer: IGamePlayer): IGamePlayer by iGamePlayer
.
.
来个例子吧
// 约束类 游戏的公司的业务,约束死的,这是规定
interface IGamePlayer {
// 打排位赛
fun rank()
// 升级
fun upgrade()
}
// 委托对象,这里 代练公司
// 这短短的一行代码,就是kotlin的委托模式精髓
class DelegateGamePlayer(private val iGamePlayer: IGamePlayer): IGamePlayer by iGamePlayer
// 被委托对象,本场景中的游戏代练
class RealGamePlayer(private val name: String): IGamePlayer{
override fun rank() {
println("打排位之前先买了瓶可乐")
println("$name 开始排位赛")
println("打排位之后可乐喝完了")
}
override fun upgrade() {
println("还没升级,没钱买泡面")
println("$name 升级了")
println("升级之后请室友吃海底捞")
}
}
fun main(){
/*val realGamePlayer = RealGamePlayer("张三")
val delegateGamePlayer = DelegateGamePlayer(realGamePlayer)*/
val delegateGamePlayer = DelegateGamePlayer(RealGamePlayer("张三"))
delegateGamePlayer.rank()
delegateGamePlayer.upgrade()
}
输出:
打排位之前先买了瓶可乐
张三 开始排位赛
打排位之后可乐喝完了
还没升级,没钱买泡面
张三 升级了
升级之后请室友吃海底捞
现在明白了吧
也许你会说,这玩意,不是跟继承差不多吗,怎么说呢,是有像,但是又不一样。毕竟这是一个设计模式,第二,你不是用by,也能实现委托模式,但是呢,by是kotlin的一个语法糖,你用kotlin,又不用语法糖,何必呢。
其实这个文章,看到这里就可以了。
.
.
不用by的委托
当然也不是说不用by就实现不了委托,这不可能。
- java的委托
- 不用by的kotlin的委托
先开看看,用Java实现一个委托,
package delegate;
interface DelegateApiJava {
void doSomething();
}
class ImplJava implements DelegateApiJava {
private DelegateApiJava delegateApiJava;
public ImplJava(DelegateApiJava delegateApiJava) {
this.delegateApiJava = delegateApiJava;
}
@Override
public void doSomething() {
if (this.delegateApiJava != null) {
System.out.println("before");
delegateApiJava.doSomething();
System.out.println("after");
}
}
}
public class Demo {
public static void main(String[] args) {
ImplJava implJava = new ImplJava(new DelegateApiJava() {
@Override
public void doSomething() {
System.out.println("doSomething");
}
});
implJava.doSomething();
}
}
// 输出(before和after就是自己的逻辑,doSomething就是代理对象的实现)
before
doSomething
after
.
.
上面的例子,如果换成Kotlin,在没有使用by的情况下,是这样的
internal interface DelegateApiJava {
fun doSomething()
}
internal class ImplJava(private val delegateApiJava: DelegateApiJava?) : DelegateApiJava {
override fun doSomething() {
if (delegateApiJava != null) {
println("before")
delegateApiJava.doSomething()
println("after")
}
}
}
object Demo {
@JvmStatic
fun main(args: Array<String>) {
val implJava = ImplJava(object : DelegateApiJava {
override fun doSomething() {
println("doSomething")
}
})
implJava.doSomething()
}
}
嗯。还是很麻烦。 .
四、使用场景:
A.延迟加载属性(lazy property): 属性值只在初次访问时才会计算,
B.可观察属性(observable property): 属性发生变化时, 可以向监听器发送通知,
C.将多个属性保存在一个 map 内, 而不是保存在多个独立的域内.
参考:
这两个文章值得一看