Kotlin開發筆記:使用委托進行拓展

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 = falseif(shouldWork && msg != null){println("work day")}else{println("relax")}
}

在這種情況下顯然就違反了短路原則,產生了額外的開銷,運行結果是:
在這里插入圖片描述
雖然msg未被使用,但是還是因為msg的賦值語句產生了額外的開銷,這顯然不是我們想要的。我們當然可以將賦值移到&&運算符之后,不過kotlin針對這種情況提供了lazy委托,讓我們對上面的例子進行修改:

fun main() {val msg by lazy { workwork() } var shouldWork = falseif(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.pswp.cn/news/40591.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/40591.shtml
英文地址,請注明出處:http://en.pswp.cn/news/40591.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

探索C語言中的常見排序算法

探索C語言中的常見排序算法 排序算法是計算機科學中至關重要的基礎知識之一&#xff0c;它們能夠幫助我們對數據進行有序排列&#xff0c;從而更高效地進行搜索、插入和刪除操作。在本篇博客中&#xff0c;我們將深入探討C語言中的一些常見排序算法&#xff0c;包括它們的工作…

在C中使用Socket實現多線程異步TCP消息發送

目錄 基礎知識開始實現主要函數說明結束語 在本篇文章中&#xff0c;我們會探討如何在C語言中使用socket來實現多線程&#xff0c;異步發送TCP消息的系統。雖然C標準庫并沒有原生支持異步和多線程編程&#xff0c;但是我們可以結合使用POSIX線程&#xff08;pthread&#xff09…

Java解決四大查找(一)

Java解決四大查找 一.線性查找1.1 題目1.2 思路分析1.3 代碼演示 二.二分查找(雙指針法)2.1 題目2.2 思路分析(圖解加文字)2.3 代碼演示 一.線性查找 1.1 題目 在數組{1&#xff0c;8&#xff0c;1024&#xff0c;521&#xff0c;1889}中查找數字8&#xff0c;如果有&#xff…

【知識分享】高防服務器的防御機制

【知識分享】高防服務器的防御機制 易受到攻擊的網站選擇接入高防服務更安全&#xff0c;大家對于這個都清楚!但是對于高防服務如何實現防御來保障安全的&#xff0c;又了解多少呢?今天壹基比小源&#xff08;貳伍壹叁壹叁壹貳玖捌&#xff09;就來說說高防服務實現防御的常規…

地址解析協議-ARP

ARP協議 無論網絡層使用何種協議&#xff0c;在實際網絡的鏈路上傳輸數據幀時&#xff0c;最終必須使用硬件地址 地址解析協議&#xff08;Address Resolution Protocol&#xff0c;ARP&#xff09;&#xff1a;完成IP地址到MAC地址的映射&#xff0c;每個主機都有一個ARP高速緩…

【數據結構】二叉樹篇| 綱領思路02+刷題

博主簡介&#xff1a;努力學習的22級計算機科學與技術本科生一枚&#x1f338;博主主頁&#xff1a; 是瑤瑤子啦每日一言&#x1f33c;: 所謂自由&#xff0c;不是隨心所欲&#xff0c;而是自我主宰。——康德 目錄 一、前言二、刷題1、翻轉二叉樹 2、二叉樹的層序遍歷?3、 二…

線性代數再回顧

最近&#xff0c;在深度學習線性代數&#xff0c;之前大一的時候學過線性代數&#xff0c;但那純屬于是應試用的&#xff0c;考試一考完&#xff0c;啥都忘了&#xff0c;也說出不出個所以然&#xff0c;所以&#xff0c;在B站的MIT的線性代數以及3blue1brown線性代數的本質中去…

git命令使用

君子拙于不知己,而信于知己。——司馬遷 清屏&#xff1a;clear 查看當前面板的路徑&#xff1a;pwd 查看當前面板的文件&#xff1a;ls 創建文件夾&#xff1a;mkdir 文件夾名 創建文件&#xff1a;touch 文件名 刪除文件夾&#xff1a;rm -rf 文件夾名 刪除文件&#xff1a;r…

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷達成像的高效實現

Remote Sensing,2023 | 基于SBL的分布式毫米波相干雷達成像的高效實現 注1&#xff1a;本文系“無線感知論文速遞”系列之一&#xff0c;致力于簡潔清晰完整地介紹、解讀無線感知領域最新的頂會/頂刊論文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI…

爬蟲IP時效問題:優化爬蟲IP使用效果實用技巧

目錄 1. 使用穩定的代理IP服務提供商&#xff1a; 2. 定期檢測代理IP的可用性&#xff1a; 3. 配置合理的代理IP切換策略&#xff1a; 4. 使用代理IP池&#xff1a; 5. 考慮代理IP的地理位置和速度&#xff1a; 6. 設置合理的請求間隔和并發量&#xff1a; 總結 在爬蟲過…

python知識:什么是字符編碼?

前言 嗨嘍&#xff0c;大家好呀~這里是愛看美女的茜茜吶 我們的MySQL使用latin1的默認字符集&#xff0c; 也就是說&#xff0c;對漢字字段直接使用GBK內碼的編碼進行存儲&#xff0c; 當需要對一些有漢字的字段進行拼音排序時&#xff08;特別涉及到類似于名字這樣的字段時…

Docker網絡與資源控制

一、Docker 網絡實現原理 Docker使用Linux橋接&#xff0c;在宿主機虛擬一個Docker容器網橋(docker0)&#xff0c;Docker啟動一個容器時會根據Docker網橋的網段分配給容器一個IP地址&#xff0c;稱為Container-IP&#xff0c;同時Docker網橋是每個容器的默認網關。因為在同一宿…

Oracle外部表ORACLE_LOADER方式加載數據

當數據源為文本或其它csv文件時&#xff0c;oracle可通過使用外部表加載數據方式&#xff0c;不需要導入可直接查詢文件內的數據。 1、如下有一個文件名為&#xff1a;test1.txt 的數據文件。數據文件內容為&#xff1a; 2、使用sys授權hr用戶可讀寫 DATA_PUMP_DIR 目錄權限&a…

探索未來:元宇宙與Web3的無限可能

隨著科技的奇跡般發展&#xff0c;互聯網已經成為了我們生活的不可分割的一部分。然而&#xff0c;盡管它的便利性和普及性帶來了巨大的影響&#xff0c;但我們仍然面臨著傳統互聯網體驗的諸多限制。 購物需要不斷在實體店與電商平臺間切換&#xff0c;教育依然受制于時間與地…

Unity如何把游戲導出成手機安裝包

文章目錄 前言使用環境步驟添加場景構建APK 前言 本文章主要演示了&#xff0c;如何將制作好的游戲&#xff0c;導出成APK&#xff0c;安裝到手機上。 使用環境 Unity2022。 步驟 首先打開你的項目&#xff0c;然后選擇菜單欄的“File” > “Build Settings…”&#xf…

QMainwindow窗口

QMainwindow窗口 菜單欄在二級菜單中輸入中文的方法給菜單欄添加相應的動作使用QMenu類的API方法添加菜單項分隔符也是QAction類 工具欄添加工具欄在狀態欄中添加控件工具欄添加其他類型的工具工具欄的屬性添加多個工具欄使用窗口添加使用代碼添加 狀態欄常用API在狀態欄顯示信…

NeuralNLP-NeuralClassifier的使用記錄(一),訓練預測自己的【英文文本多分類】

NeuralNLP-NeuralClassifier的使用記錄&#xff0c;訓練預測自己的英文文本多分類 NeuralNLP-NeuralClassifier是騰訊開發的一個多層多分類應用工具&#xff0c;支持的任務包括&#xff0c;文本分類中的二分類、多分類、多標簽&#xff0c;以及層次多標簽分類。支持的文本編碼…

C語言庫函數之 qsort 講解、使用及模擬實現

引入 我們在學習排序的時候&#xff0c;第一個接觸到的應該都是冒泡排序&#xff0c;我們先來復習一下冒泡排序的代碼&#xff0c;來作為一個鋪墊和引入。 代碼如下&#xff1a; #include<stdio.h>void bubble_sort(int *arr, int sz) {int i 0;for (i 0; i < sz…

面試熱題(最大子數組和)

給你一個整數數組 nums &#xff0c;請你找出一個具有最大和的連續子數組&#xff08;子數組最少包含一個元素&#xff09;&#xff0c;返回其最大和。 子數組 是數組中的一個連續部分。 輸入&#xff1a;nums [-2,1,-3,4,-1,2,1,-5,4] 輸出&#xff1a;6 解釋&#xff1a;連續…

免費批量ppt轉pdf?一個方法教你完美轉換

隨著科技的不斷發展&#xff0c;電子文檔的使用越來越普遍。在商業、教育和個人領域&#xff0c;我們經常需要將PPT文件轉換為PDF格式&#xff0c;以便更方便地共享和存檔。幸運的是&#xff0c;現在有許多在線工具和軟件可以幫助我們輕松地完成免費批量ppt轉pdf。下面將介紹一…