kotlin委托

JAVA代理模式

代理模式定义

为其他对象提供一种代理以控制对象对这个对象的访问。

代理模式的使用场景

当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,委托对象与代理对象需要实现相同的接口。

代理模式UML类图

代理模式(2).png

抽象主题或接口类

public abstract class Subject {
    /**
     * 一个普通的业务方法
     */
    public abstract void visit();
}

实现抽象主题的真实主题类

public class RealSubject extends Subject {
    @Override
    public void visit() {
        //RealSubject中的visit的具体逻辑
        System.out.print("Real subject");
    }
}

代理类

public class ProxySubject extends Subject {
    //持有真正主题的引用
    private RealSubject mSubject;

    //通过构造传入真正主主题的引用
    public ProxySubject(RealSubject mSubject) {
        this.mSubject = mSubject;
    }

    @Override
    public void visit() {
        //通过真实主题的引用的对象调用真实主题中的逻辑方法
        mSubject.visit();
    }
}

客户类

public class Client {
    public static void main(String[] args){
        //构造一个真实的主题对象
        RealSubject realSubject=new RealSubject();
        //通过真实的主题对象构造一个代理对象
        ProxySubject proxySubject=new ProxySubject(realSubject);
        //调用代理相关方法
        proxySubject.visit();
    }
}

角色介绍
Subject:抽象主题类
该类的主要职责是声明真实主题的共同接口方法,该类可以 是一个抽象类也可以 是一个接口。
RealSubject:真实主题类
该类也称为被委托类或被代理类,该类定义了代理所表示的真实对象,由其执行具体的业务逻辑方法。
ProxySubject:代理类。
该类也称为委托类或代理类,该类持有一个对真实主题类的引用 ,在其所实现的接口方法 中调用真实主题类中相应 的接口方法。

动态代理

代理模式大致分为两部分,一是静态代理 ,二是动态代理,静态代理如上述示例那样,代理者的代码由程序员自己或通过一些自动化工具生成固定的代码再对其进行编译,也就是说在我们的代码运行前代理类的class编译文件就已经存在,而动态代理则与静态代理相反,通过反射机制动态的生成代理对象,也就是说我们在code阶段压根就不需要知道代理谁,代理谁我们将会在执行阶段决定,而Java也给我们提供了一个便捷的动态代理接口InvocationHandler,实现该接口需要重写其调用方法invoke()
抽象主题类

public interface Subject {
    /**
     * 一个普通的业务方法
     */
    void visit();
}

动态代理类

public class DynamicProxy implements InvocationHandler {
    private Object object;//被代理类的引用
    public DynamicProxy(Object object){
        this.object=object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       //调用被代理类对象的方法
        Object result=method.invoke(object,args);
        return result;
    }
}

客户端类

public class Client {
    public static void main(String[] args){
        //构造一个真实的主题对象
        RealSubject realSubject=new RealSubject();
        //构造一个动态代理
        DynamicProxy proxy=new DynamicProxy(realSubject);
        //获取一个真实代理类的ClassLoader
        ClassLoader loader=realSubject.getClass().getClassLoader();
        //动态构造一个代理类
        Subject subject=(Subject) Proxy.newProxyInstance(loader,new Class[]{Subject.class},proxy);
        //调用代理相关方法
        subject.visit();
    }
}

kotlin委托

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。

类委托

类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。

// 创建接口
interface Base {   
    fun print()
}

// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。
属性委托语法格式:

val/var <属性名>: <类型> by <表达式>
var/val:属性类型(可变/只读)
属性名:属性名称
类型:属性的数据类型
表达式:委托代理类

by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。
定义一个被委托的类
该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,property 为进行委托的属性的对象。

import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example {
    var p: String by Delegate()
}

// 委托的类
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 这里委托了 ${property.name} 属性"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$thisRef 的 ${property.name} 属性赋值为 $value")
    }
}
fun main(args: Array<String>) {
    val e = Example()
    println(e.p)     // 访问该属性,调用 getValue() 函数

    e.p = "Runoob"   // 调用 setValue() 函数
    println(e.p)
}
输出结果为:
Example@433c675d, 这里委托了 p 属性
Example@433c675d 的 p 属性赋值为 Runoob
Example@433c675d, 这里委托了 p 属性

标准委托

Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。

延迟属性 Lazy

lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    println("computed!")     // 第一次调用输出,第二次调用不执行
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)   // 第一次执行,执行两次输出表达式
    println("--------")
    println(lazyValue)   // 第二次执行,只输出返回值
}
执行输出结果:
computed!
Hello
--------
Hello

lazy()的使用

惰性初始化是一种常见的模式,直到在第一次访问该属性的时候,才根据需要创建对象的一部分,当初始化过程消耗大量资源并且在使用对象时并不总是需要数据时,这个就非常有用。
举个例子,一个Person类,可以用来访问一个人写的邮件列表。邮件存储在数据库中,访问比较耗时。你希望只有在首次访问时才加载邮件,并且只执行一次

class Email{/*...*/}
//用来从数据库中检索电子邮件的函数
fun loadEmails(person: Person):List<Email>{
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}

下面展示了如何使用额外的_emails属性来实现惰性加载,在没有加载之前为null,然后加载为邮件列表

class Person(var name:String){
    //_emails属性来保存数据,关联委托
    private  var _emails:List<Email>?=null

    val emails:List<Email>
    get() {
        if (_emails==null){
            //访问时加载邮件
            _emails=loadEmails(this)
        }
        //如果以经加载直接返回
        return _emails!!
    }
}

fun main(args: Array<String>) {
    val p=Person("sanshi")
    p.emails//第一次访问会加载邮件
    println("-----")
    p.emails//第二次访问就不会加载邮件
}
输出为
Load emails for sanshi
-----

这里使用了所谓的支持属性技术。你有一个属性,_emails,用来存储这个值,面另一个emails,用来提供对属性的读取访问。你需要使用两个属性,因为属性有不同的类型:_emails可以为空,而emails为非空。
但这个代码有点啰嗦,而且他还是线程不安全的。所以kotlin提供了更好的解决方案:by layz()

class Email{/*...*/}
//用来从数据库中检索电子邮件的函数
fun loadEmails(person: Person):List<Email>{
    println("Load emails for ${person.name}")
    return listOf(/*...*/)
}
class Person(var name:String){
   val emails by lazy {
       loadEmails(this)
   }
}

fun main(args: Array<String>) {
    val p=Person("sanshi")
    p.emails//第一次访问会加载邮件
    println("-----")
    p.emails//第二次访问就不会加载邮件
}
输出为
Load emails for sanshi
-----

lazy函数返回一个对象,该对象具有一个名为getValue且签名正确的方法,因此可以把它与by关键字一起使用来创建一个委托属性。lazy函数是线程安全的,如果需要设置其他选项来告诉它要使用哪个锁,或者完全避开同步。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容