Kotlin技术知识我爱编程

kotlin委托

2018-06-13  本文已影响2469人  哈哈_defb

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函数是线程安全的,如果需要设置其他选项来告诉它要使用哪个锁,或者完全避开同步。

上一篇下一篇

猜你喜欢

热点阅读