Kotlin开发笔记:使用委托进行拓展

news2024/9/25 15:25:12

Kotlin开发笔记:使用委托进行拓展

在这里插入图片描述

导言

在OO语言(面向对象)中,我们经常会用到委托或者代理的思想。委托和代理在乍一看很相似,其实其各有各的侧重点,这里我引用ChatGpt的回答:

委托(Delegation)和代理(Proxy)虽然有相似之处,但在面向对象编程中有一些区别。

  • 职责分配:
    委托:委托是一种将对象的一部分职责转交给另一个对象来处理的方式。原始对象将某些任务委托给另一个对象,但是仍然保持对委托对象的控制。
    代理:代理是一种通过提供一个代替对象来控制访问。代理对象通常具有与被代理对象相同的接口,客户端代码可以通过代理对象来间接访问被代理对象。
    目的:
  • 委托:委托用于实现代码的模块化和职责分离,将一部分功能委托给其他对象处理,以达到更好的代码组织和可维护性。
  • 代理:代理用于控制访问,可以用于实现懒加载、安全性控制、远程访问等。代理对象可以在客户端和被代理对象之间添加额外的逻辑,如缓存、权限验证等。
    关系类型:
  • 委托:委托通常涉及到两个具体的对象,一个是原始对象,另一个是被委托的对象,它们可以属于不同的类。
  • 代理:代理通常有三个主要组成部分:客户端、代理对象和被代理对象。代理对象扮演中间人的角色,控制客户端访问被代理对象。

虽然委托和代理在某些情况下可能会有重叠,但它们的重点和使用方式是不同的。
委托更关注职责分离和模块化,而代理更关注控制访问和添加额外的逻辑。在实际编程中,选择使用委托还是代理取决于具体的需求和设计目标。

通过前面的介绍我们应该对代理和委托的概念和区别有了一定的认识。不过本篇文章并不是来探讨委托和代理之间的关系,而是来简单介绍Kotlin中的委托语法相关知识的。

使用Kotlin的by来进行委托

在Java中并没有专门的语法来帮助我们实现委托,而Kotlin中则十分贴心地提供了by关键字,通过这个关键字我们可以要求编译器生成粗略的代码来帮助我们实现委托。接下来给出一个最简单的例子来介绍by关键字的用法。

首先我们先定义一个Worker接口来定义打工人的职责:

interface Worker {
    fun work()
    fun takeVacation()
}

该接口有work和takeVacation(不存在的)两个方法。接下来定义两个类来实现这个接口:

class JavaProgramer : Worker{
    override fun work() {
        println("... write JavaCode ...")
    }

    override fun takeVacation() {
        println("JavaProgramer relax")
    }
}

class CSharpProgramer : Worker{
    override fun work() {
        println("...writer CSharpCode ...")
    }

    override fun takeVacation() {
        println("CSharpProgramer relax")
    }
}

最后,我们还想要定义一个Manager类来管理所有的Worker,这种情况下我们就可以使用到Kotlin中的by关键字进行委托:

class Manager():Worker by JavaProgramer()//委托语法

是的,只需要这一行Manager就实现了委托,这种情况下我们调用Manager来调用方法最终就会路由到JavaProgramer的一个默认生成的实例中运行:

fun main() {
    val del = Manager()
    del.work()
}

最后的结果就是:
在这里插入图片描述
其实这样说可能不太清楚,我们来仔细分析一下class Manager():Worker by JavaProgramer()这行代码,首先类名后面用冒号跟上Worker接口的意思正是Manager类需要实现Worker接口,后面的JavaProgramer()代码就会自动生成一个JavaProgramer的实例,最后通过by连接,意思就是Manager类将会委托后面生成的这个JavaProgramer实例来实现Worker接口。

上面例子的局限

上面的这个例子的局限也十分明显,那就是由于委托的JavaProgramer实例是隐性生成的,所以我们就丢失了对委托的引用,这种情况下我们在这个Manager类就无法再次委托隐式生成的JavaProgramer来进行一些操作了。

委托给一个参数

上面我们提出了上面例子的局限性,不过只要理解了我们在上面分析的那一行代码的语法,我们可以很简单地避免上面的局限性。很显然要解决这个问题需要我们保留对被委托方的引用:

class Manager2(val mWorker: Worker):Worker by mWorker{
    fun fun1(){
        mWorker.work()
    }
}

在这段代码中我们保留了对被委托方的引用,通过幕后生成的mWorker字段存储了被委托方,后面的by mWorker一句表明这个Manager2类将会委托mWorker字段来实现Worker接口。

不要用var来修饰持有委托的字段

上面的代码中我们用val变量持有了传入的委托实例,当然编译器也是允许我们使用var变量来持有委托实例的,不过这样做存在风险。具体来说class Manager2(val mWorker: Worker):Worker by mWorker 实际上存储了两个对mWorker的引用,一个就是通过val生成的幕后字段,还有一个就是通过by生成的包装类中持有的委托引用。当我们用val修饰时不会有什么问题,但是如果当我们用var修饰就会存在隐患

比如我们这样写:

class Manager2(var mWorker: Worker):Worker by mWorker{
    fun change(){
        if(mWorker is JavaProgramer){
            mWorker = CSharpProgramer()
        }else{
            mWorker = JavaProgramer()
        }
    }

    fun showWorker(){
        println(mWorker.javaClass.simpleName)
    }
}

里面定义的change方法将会更改成员变量中的mWorker但是无法修改by语句的委托实例,我们运行一下这段代码查看结果:

fun main() {
    val del = Manager2(JavaProgramer())
    del.work()
    del.takeVacation()
    del.showWorker()
    del.change()
    del.work()
    del.takeVacation()
    del.showWorker()
}

最后结果为:
在这里插入图片描述
这里虽然成员变量中持有的Worker发生了变化,但是委托的Worker依旧是一开始创建的Worker,它无法被修改,这样就会造成语义的不清晰,所以说我们尽量不要用var变量来持有被委托的实例。

取消部分委托

当我们的委托方类没有和接口中的方法名一致的方法时,将不会有什么大问题,但是如果委托方中有一个方法实现了接口中的一些方法时就会和委托产生冲突。换句话说,如果我们只想要委托给一个实例实现接口中的部分方法时就需要处理掉一些冲突。比如我们在之前的例子中进行修改,如果Manager的代码如下就会产生冲突:

class Manager():Worker by JavaProgramer(){
    fun takeVacation(){ 
        println("Manager Relax...")
    }
}//委托语法

这种情况下被委托方中的takeVacation方法就和Manager类中的takeVacation方法有了冲突,编译器无法决定是该调用被委托方的方法还是Manager中的方法。这时就需要在我们不需要进行代理的方法前加上override修饰符,如下:

class Manager():Worker by JavaProgramer(){
    override fun takeVacation(){ //解决方法冲突--取消委托
        println("Manager Relax...")
    }
}//委托语法

这种语法我们可以理解为单个方法取消委托,也可以理解为Manager类在实现接口的方法。总而言之,我们这样表达后冲突就不复存在了,当我们调用Manager类的takeVacation方法时就会调用Manager自身的方法而不是进行委托。

实现多个委托

上面的情况我们介绍的都是一个类委托给一个类实现,实际上一个类也可以委托给多个类实现多个接口,不过这种情况下可能会产生一些冲突需要我们手动处理。接下来我们修改Worker接口并且新增一个Assistant接口:

interface Worker {
    fun work()
    fun takeVacation()
    fun FishingTime()
}

interface Assistant{
    fun doChores()
    fun FishingTime()
}

这样我们这两个接口就会有一个重叠的方法,现在我们创建一个类来实现多个委托:

class Manager3(val mWorker: Worker,val mAssistant: Assistant):Worker by mWorker,
Assistant by mAssistant{

}

这样写将会产生报错,因为这两个接口有一个重叠的方法,用两个委托的话编译器将无法确定Manager3需要委托哪一个类来实现FishingTime方法,这里解决冲突的方法就是使用取消委托的方式,用override进行修饰决定到底需要哪一个委托:

class Manager3(val mWorker: Worker,val mAssistant: Assistant):Worker by mWorker,
Assistant by mAssistant{
    override fun FishingTime() {
        mWorker.FishingTime()
        mAssistant.FishingTime()
    }
}

这样就解决了冲突,同时实现了一个委托类委托给多个类实现的效果。

内置的标准委托

Lazy委托

在这里Lazy委托也可以被理解为懒加载,即只有在真正需要时才对一个函数进行调用,以达到节省开销延时加载计算的效果。比如说在布尔逻辑表达式中现在的大部分语言都有短路求值的特性,如果在表达式之前对表达式的求值足以产生结果,则跳过表达式的执行。比如:

fun workwork():String{
    println("execute")
    return "work work"
}

fun main() {
    val msg = workwork()
    var shouldWork = false
    if(shouldWork && msg != null){
        println("work day")
    }else{
        println("relax")
    }
}

在这种情况下显然就违反了短路原则,产生了额外的开销,运行结果是:
在这里插入图片描述
虽然msg未被使用,但是还是因为msg的赋值语句产生了额外的开销,这显然不是我们想要的。我们当然可以将赋值移到&&运算符之后,不过kotlin针对这种情况提供了lazy委托,让我们对上面的例子进行修改:

fun main() {
    val msg by lazy { workwork() } 
    var shouldWork = false
    if(shouldWork && msg != null){
        println("work day")
    }else{
        println("relax")
    }
}

这里我们用lazy委托包装了workwork函数,现在再来看运行结果:
在这里插入图片描述
额外的开销消失了,很神奇。这就是Kotlin中的lazy委托。Lazy委托后面接收一个lambda表达式,这样在我们需要用到委托方(在这里即为msg变量)时才会执行,否则将不会被执行,也就是说它是按需执行的。一旦对lambda中表达式求值,委托将记住结果,以后对该值的请求将接受保存的值而不是重新计算lambda表达式。

在默认情况下,lazy函数同步lambda表达式的运行,因此最多只有一个线程运行它。另外,Kotlin中的lazy委托只能用于val(不可变)变量,而不能用于var(可变)变量。这是因为lazy委托的特性与惰性求值相关,适用于只需要初始化一次并且后续不会再变化的情况。

Observable委托

接下来介绍的是Observable委托,看名字就知道这个委托和观察者模式密不可分。实际上也是这样。Observable委托将对关联的变量或者属性的修改进行拦截,发生修改时委托将调用我们用observable函数注册的事件处理程序上。

事件处理程序将接受三个类型为KProperty的参数,这些参数保存关于属性,旧值和新值的元数据,但是不返回任何值。我们直接用例子来说明:

fun main() {
    var count by observable(0){property, oldValue, newValue ->
        println("参数是:$property,旧值是:$oldValue,新值是:$newValue")
    }
    count++
    count++
    count++
}

这里我们用observable委托将count参数给委托了,observable括号中的0代表的是初始值,也就是count一开始为0,而当我们对count进行修改时就会触发后面的lambda表达式,我们来看看运行结果:
在这里插入图片描述
显然我们对count进行修改时就触发了这段lambda表达式,达到了观察的效果,感觉和JetPack中的LiveData也很相似。

vetoable委托

接下来介绍的是vetoable委托,和observable委托不同的是vetoable将返回一个Boolean类的值,如果返回true代表同意修改,否则就是拒绝修改。一旦拒绝修改,被委托的变量也就将停止修改,比如说:

fun main() {
    var count by vetoable(0){property, oldValue, newValue ->
        println("参数是:$property,旧值是:$oldValue,新值是:$newValue")
        oldValue < newValue
    }
    count++
    count--
    count--
}

这里我们对上面的例子稍作修改,lambda表达式最后一行的oldValue < newValue就是vetoable委托的最后返回值,当新值大于旧值时才同意修改,所以可以预见的是count–将不会生效,我们来看运行结果:
在这里插入图片描述
可以看到,后面两次修改果然没有生效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/889975.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

内涝防控一张图!解锁内涝防控全周期解决方案

随着暴雨高发季的到来&#xff0c;如何提升城市内涝治理水平&#xff0c;构筑人民生活美好空间成为各地关注的重点。近日&#xff0c; 住房和城乡建设部、应急管理部印发《关于加强城市排水防涝应急管理工作的通知》&#xff0c;明确提出&#xff0c;各地要加强城市内涝整治和排…

java Spring Boot properties多环境配置拆分文件管理

上文 java Spring Boot yml多环境拆分文件管理优化 我们用yml 做了一个多环境配置文件的拆分管理 我们将 application.yml 改为 application.properties 参考代码如下 spring.profiles.activedev我们知道 yml 是用 : 来区分高低基本 而 properties是直接通过 . 来表达 其他基本…

解锁暑假云端生活:铁威马NAS助你打造个性化体验

暑假转眼过半&#xff0c;大家一定度过一段非常美好的时光吧。朋友圈被去各地旅游的、看各种演唱会的、各种各样的观影读后感刷屏...生活很精彩&#xff0c;但如何高效地管理、享受和分享自己的文件、照片和影音内容成为困扰我们的难题。在这方面&#xff0c;铁威马NAS成为了越…

Unity自定义脚本的 初始模版

参考博主&#xff1a;Unity修改创建的脚本模板&#xff0c;Unity脚本模板路径_unity hub 怎么改脚本模板_先生沉默先的博客-CSDN博客 【100个 Unity实用技能】 ☀️ | Unity自定义脚本的初始模版_unity 模板脚本_呆呆敲代码的小Y的博客-CSDN博客 一&#xff0c;将脚本放到Ed…

【uniapp】使用permission获取录音权限及实现录音功能

需求 app获取录音权限权限, 实现录音并且播放功能 实现 一. 使用permission获取录音权限 原博 : https://www.wanjunshijie.com/note/uniapp/3203.html 1.1 manifest.json 配置权限 android.permission.RECORD_AUDIO 1.2 permision使用和下载 ( 自行百度搜索即可 ) 1.3 获…

【探索Linux】—— 强大的命令行工具 P.4(编译器 gcc/g++ 使用)

阅读导航 前言一、编译的四大过程&#xff08;背景知识&#xff09;1. 预处理&#xff08;Preprocessing&#xff09;2. 编译&#xff08;Compilation&#xff09;3. 汇编&#xff08;Assembly&#xff09;4. 链接&#xff08;Linking&#xff09; 二、gcc的使用1. 概念2. gcc主…

Docker 容器内无法使用vim命令 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 进入Docker容器后 无法使用vim编辑器,出现如下问题:bash: vim: command not found 如图所示: 想着通过apt-get 安装vim,出现如下问题: root@b9f0fd330d5b:/# apt-get install vim Reading package lists... Done B…

VR全景加盟项目如何开展?如何共赢VR时代红利?

VR全景作为一个新兴蓝海项目&#xff0c;相信有着很多人刚接触VR行业的时候都会有这样的疑问&#xff1a;VR全景加盟后项目如何开展&#xff1f;今天&#xff0c;我们就从项目运营的三个阶段为大家讲解。 一、了解项目时 目前VR全景已经被应用到各行各业中去&#xff0c;学校、…

通过postgresql的Ltree字段类型实现目录结构的基本操作

通过postgresql的Ltree字段类型实现目录结构的基本操作 将这种具有目录结构的excel表存储到数据库中&#xff0c;可以采用树型结构存储 DROP TABLE IF EXISTS "public"."directory_tree"; CREATE TABLE "public"."directory_tree" (…

使用 PyTorch 进行高效图像分割:第 1 部分

一、说明 在这个由 4 部分组成的系列中&#xff0c;我们将使用 PyTorch 中的深度学习技术从头开始逐步实现图像分割。我们将在本文中从图像分割所需的基本概念和想法开始本系列。 图1&#xff1a;宠物图像及其分割掩码&#xff08;来源&#xff1a;牛津-IIIT宠物数据集) 图像分…

OpenLayers入门,OpenLayers加载船讯网航海地图

专栏目录: OpenLayers入门教程汇总目录 前言 本章实现OpenLayers加载船讯网航海地图。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@6.15.1使用Yarn安装依赖yarn add olvue中如何使用: vue项目使用请参考这篇文章:

Maven之JDK编译问题

IDEA Maven 默认使用 JDK 1.5 编译问题 IDEA 在「调用」maven 时&#xff0c;IDEA 默认都会采用 JDK 1.5 编译&#xff0c;不管你安装的 JDK 版本是 JDK 7 还是 JDK 8 或者更高。这样一来非常不方便&#xff0c;尤其是时不时使用 JDK 7/8 的新特性时。如果使用新特性&#xff…

[10min速通]STM32CubemMX配置W25Q128

[10min速通]&#x1f98f;STM32CubemMX配置W25Q128 文章目录 [10min速通]&#x1f98f;STM32CubemMX配置W25Q1281、下载源码2、配置Cube2.1 基础配置2.2 SPI配置 3、配置MDK3.1 添加源文件3.2 管理源文件3.3 完成接口配置 4、接口介绍4.1 初始化4.2 擦除4.3 写入4.4 读取 5、代…

AntDB数据库受邀参加【ACDU 中国行】,共促行业发展和创新

作为数据的集中存储和管理系统&#xff0c;数据库在现代信息化时代扮演着至关重要的角色&#xff0c;随着人工智能&#xff0c;物联网和大数据时代的到来&#xff0c;数据库的发展需要进一步拓展其广度和深度&#xff0c;持续创新&#xff0c;实现技术进步&#xff0c;以更好地…

香港服务器备案会通过吗?

​  对于企业或个人来说&#xff0c;合规备案是网络运营的基本要求&#xff0c;也是保护自身权益的重要举措。以下内容围绕备案展开话题&#xff0c;希望为您解开疑惑。 香港服务器备案会通过吗? 目前&#xff0c;香港服务器无法备案&#xff0c;这是由于国内管理规定的限制…

Salient主题 - 创意多用途和WooCommerce商城主题

Salient主题是下一代WordPress主题&#xff0c;给任何人带来专业的设计结果&#xff0c;而不需要任何编码。Salient 提供永久更新的专业剖面模板库&#xff0c;目前有超过425个可供选择 – 所有这些都充满热情并坚持高标准的审美质量。 网址: Salient主题 - 创意多用途和WooCo…

Linux下安装nodejs

1、下载nodejs 点击前往&#xff1a;Download | Node.js 2、解压 tar -xvf node-v18.16.0-linux-x64.tar.xz mv node-v18.16.0-linux-x64/ /usr/local/nodejs 3、 建立软链接 此时的bin文件夹中已经存在node以及npm&#xff0c;如果你进入到对应文件的中执行命令行一点问题…

vue3 路由缓存问题

目录 解决问题的思路&#xff1a; 解决问题的方案&#xff1a; 1、给roter-view添加key&#xff08;破坏复用机制&#xff0c;强制销毁重建&#xff09; 2、使用beforeRouteUpdate导航钩子 3、使用watch监听路由 vue3路由缓存&#xff1a;当用户从/users/johnny导航到/use…

估算森林蓄积量哨兵2号变量计算详情

哨兵2号估算森林蓄积量变量计算详细步骤 变量概括哨兵2号变量计算详情1. 生物物理变量计算2. 植被指数3. 纹理特征 变量概括 需要计算的变量参数有&#xff1a; 生物物理变量 采用SNAP软件的工具生成5个生物物理变量。植被指数 采用gdal库计算&#xff0c;共17个植被指数变量…

「爱校对」——如何让每一篇公文都完美无瑕

在繁忙的办公环境中&#xff0c;公文是企业沟通的桥梁和载体&#xff0c;每一篇公文都关乎公司的形象和效率。但是&#xff0c;即使是最细心的员工&#xff0c;也难免会在公文中出现笔误或格式错误。这时&#xff0c;「爱校对」就显得尤为重要。 1.什么是「爱校对」&#xff1f…