interface IDataPersistence {
fun addData()
fun delData()
fun queryData()
}
第二步:创建委托的实现,实现约束接口。数据持久化有多种不同的实现方式,下面这就是简单的两种,一种是通过SQL进行持久化,另一种是通过SharedPreferences进行持久化。
class SQL : IDataPersistence {
override fun addData() {
Log.d("xys", "addData with SQL")
}
override fun delData() {
Log.d("xys", "delData with SQL")
}
override fun queryData() {
Log.d("xys", "queryData with SQL")
}
}
class SharedPreferences : IDataPersistence {
override fun addData() {
Log.d("xys", "addData with SharedPreferences")
}
override fun delData() {
Log.d("xys", "delData with SharedPreferences")
}
override fun queryData() {
Log.d("xys", "queryData with SharedPreferences")
}
}
第三步:调用约束接口,即业务方调用,但不用考虑具体的实现。类委托的语法格式是,<类>:<约束接口> by <实现类的实例>,即通过by关键字,将接口的实现,委托给一个具体的实例来作为自己的实现。
class MyDB(private val delegate: IDataPersistence) : IDataPersistence by delegate
使用方式与Java代码通过接口来实现基本一致,即在类初始化的时候,传入具体的实现类即可。
// val myDB = MyDB(SQL())
val myDB = MyDB(SharedPreferences())
myDB.addData()
myDB.delData()
myDB.queryData()
在Kotlin的类委托机制中,调用方和业务实现方,都需要实现约束接口,调用方只需要传入不同类型的业务实现方式,即可通过约束调用具体的实现。这一点看上去好像并没有比Java方便多少,但是在Kotlin中,在某些简单的场景下,实际上是可以省略掉实现类的,直接通过对委托实现的重写来实现委托接口,代码如下所示。
class MyDB(private val delegate: IDataPersistence) : IDataPersistence by delegate {
override fun addData() {}
override fun delData() {}
override fun queryData() {}
}
再简单一点,如果你不用传入多种不同的实例,可以在构造方法中去掉默认参数,直接在by关键字后面添加具体的接口实现,还是上面的例子,代码如下所示。
class MyDB : IDataPersistence by SQL()
调用:
MyDB().addData()
通过委托,可以在不影响继承(MyDB可以继承其它类)的情况下,通过委托,使用指定接口中的方法。

class RedSquare : Shape by Square(), Color by Red() {
fun draw() {
print("draw Square with Red")
}
}
另外,委托还可以用于在不修改原来代码及架构的基础上,对原有功能扩展或者修改。例如我们要对MutableList类拓展一个函数,如果是Java代码,或者不使用委托的Kotlin代码,你必须实现List接口中的所有函数,虽然你未作修改,只是单纯的传递调用,但是需要为这个拓展写很多无用的代码,而使用委托,则完全不用处理这些冗余,代码如下所示。
class NewList(private val list: MutableList<String>) : MutableList<String> by list {
fun newFunction() {}
}
Kotlin会自动在编译时帮你添加其它接口方法的默认实现。
val/var <属性名>: <类型> by <表达式>
在前面的讲解中,类委托,委托的是接口中指定的方法,而属性委托,则委托的是属性的get、set方法,属性委托实际上就是将get、set方法的逻辑委托给一个单独的类来进行实现(对于val属性来说,委托的是getValue方法,对于var属性来说,委托的是setValue和getValue方法)。
属性委托在那些需要对属性的get、set方法复用逻辑的场景下,是非常方便的,下面通过一个简单的例子来演示下属性委托机制。
首先,我们定义一个var属性,并将其委托给MyDelegate类,即将get和set方法进行了交接托管,因此,MyDelegate类需要重写getValue和setValue方法,为其提供新的返回值和逻辑,代码如下所示。
var delegateProp by MyDelegate()
class MyDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "MyDelegate get $thisRef ${property.name}"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
Log.d("xys", "MyDelegate set $value $thisRef ${property.name}")
}
}
调用:
Log.d("xys", delegateProp)
delegateProp = "abc"
out:
com.yw.demo D/xys: MyDelegate get com.yw.demo.MainActivity@595c528 delegateProp
com.yw.demo D/xys: MyDelegate set abc com.yw.demo.MainActivity@595c528 delegateProp
这样处理之后,我们在使用delegateProp这个属性的时候,就会自动拓展MyDelegate中的处理。
不过呢,这样写起来太麻烦,MyDelegate中的方法都需要手动来实现,所以Kotlin提供了两个接口来帮助开发者实现。

class MyDelegate : ReadWriteProperty<Any, String> {
override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
Log.d("xys", "MyDelegate set $value $thisRef ${property.name}")
}
override fun getValue(thisRef: Any, property: KProperty<*>): String {
return "MyDelegate get $thisRef ${property.name}"
}
}
object Prop {
var encodeProp: String by EncodeProperty("init")
}
class EncodeProperty(var value: String) : ReadWriteProperty<Prop, String> {
override fun getValue(thisRef: Prop, property: KProperty<*>): String {
return "get encode prop output $value"
}
override fun setValue(thisRef: Prop, property: KProperty<*>, value: String) {
this.value = value
Log.d("xys", "save encode prop $value")
}
}
调用:
Prop.encodeProp = "xuyisheng"
Log.d("xys", Prop.encodeProp)
参数依然是那个参数变量,但是对它的处理被外包出去,交给了EncodeProperty来进行处理,这里的实现就是业务需要的encode操作,将来如果encode操作有改动,那么只需要修改EncodeProperty即可。也就是说,我们将encode的具体逻辑进行了封装,这样便于拓展和维护。
class Person {
var firstName: String = ""
set(value) {
field = value.toLowerCase()
}
var lastname: String = ""
set(value) {
field = value.toLowerCase()
}
}
调用:
val person = Person()
person.firstName = "XU"
person.lastname = "YISHENG"
println("${person.firstName} ${person.lastname}")
但是这里的两个属性的set方法,要处理的逻辑基本是一样的,即对字母做小写,所以我们对这个操作进行抽取,设置一个委托,代码如下所示。
class FormatDelegate : ReadWriteProperty<Any?, String> {
private var formattedString: String = ""
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return formattedString
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
formattedString = value.toLowerCase()
}
}
这个委托做的事情,和在前面的代码中set的逻辑是一样的。那么这个时候,就可以对Person类进行改造了,代码如下所示。
class Person {
var firstName: String by FormatDelegate()
var lastname: String by FormatDelegate()
}
这样就将同样的set操作的逻辑,封装在了FormatDelegate中,从而实现了模板代码的消除。
const val PARAM1 = "param1"
const val PARAM2 = "param2"
class DemoFragment : Fragment() {
private var param1: Int? = null
private var param2: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { args ->
param1 = args.getInt(PARAM1)
param2 = args.getString(PARAM2)
}
}
companion object {
fun newInstance(param1: Int, param2: String): DemoFragment =
DemoFragment().apply {
arguments = Bundle().apply {
putInt(PARAM1, param1)
putString(PARAM2, param2)
}
}
}
}
首先,我们可以通过Kotlin的set、get函数进行改写,将arguments的填充,放到属性的get、set函数内部,代码如下所示。
class DemoFragment : Fragment() {
private var param1: Int?
get() = arguments?.getInt(PARAM1)
set(value) {
value?.let {
arguments?.putInt(PARAM1, it)
}
}
private var param2: String?
get() = arguments?.getString(PARAM2)
set(value) {
arguments?.putString(PARAM2, value)
}
companion object {
fun newInstance(param1: Int, param2: String): DemoFragment =
DemoFragment().apply {
this.param1 = param1
this.param2 = param2
}
}
}
但是我们还是要为每个属性写重复的代码,特别是当属性很多的时候,每个属性都要写重复的put、get函数,所以,下面使用委托对这个逻辑再进行一次封装,代码如下所示。
class DemoFragment : Fragment() {
private var param1: Int by FragmentArgumentDelegate()
private var param2: String by FragmentArgumentDelegate()
companion object {
fun newInstance(param1: Int, param2: String): DemoFragment =
DemoFragment().apply {
this.param1 = param1
this.param2 = param2
}
}
}
@Suppress("UNCHECKED_CAST")
class FragmentArgumentDelegate<T : Any> : ReadWriteProperty<Fragment, T> {
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
val key = property.name
return thisRef.arguments?.get(key) as T
}
override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
val arguments = thisRef.arguments
val key = property.name
arguments?.put(key, value)
}
}
fun <T> Bundle.put(key: String, value: T) {
when (value) {
is Boolean -> putBoolean(key, value)
is String -> putString(key, value)
is Int -> putInt(key, value)
is Short -> putShort(key, value)
is Long -> putLong(key, value)
is Byte -> putByte(key, value)
is ByteArray -> putByteArray(key, value)
is Char -> putChar(key, value)
is CharArray -> putCharArray(key, value)
is CharSequence -> putCharSequence(key, value)
is Float -> putFloat(key, value)
is Bundle -> putBundle(key, value)
is Parcelable -> putParcelable(key, value)
is Serializable -> putSerializable(key, value)
else -> throw IllegalStateException("Type of property $key is not supported")
}
}
这里要注意的是,Bundle没有提供单个属性的put拓展,所以我们需要自己实现一个。
通过上面的这些操作,就将Fragment参数传递的代码简化到了只有一行,其它任何的Fragment传参,都可以使用这个委托。
private val lazyProp: String by lazy {
Log.d("xys", "表达式只会执行一次")
"执行后赋值给lazyProp"
}
Log.d("xys", lazyProp)
Log.d("xys", lazyProp)
out:
D/xys: 表达式只会执行一次
D/xys: 执行后赋值给lazyProp
D/xys: 执行后赋值给lazyProp
要注意的是,lazy表达式中的代码,只会在第一次初始化的时候调用,之后就不会调用了,所以这里log只打印了一次。
var observableProp: String by Delegates.observable("init value 0") { property, oldValue, newValue ->
Log.d("xys", "change: $property: $oldValue -> $newValue ")
}
Log.d("xys", observableProp)
observableProp = "change value"
当属性值发生改变的时候,就会通知出来。
借助观察属性,可以很方便的实现时间差的判断,例如连续back退出的功能,代码如下所示。
private var backPressedTime by Delegates.observable(0L) { pre, old, new ->
if (new - old < 2000) {
finish()
} else {
Toast.makeText(this, "再按一次返回退出", Toast.LENGTH_SHORT).show()
}
}
override fun onBackPressed() {
backPressedTime = System.currentTimeMillis()
}
var vetoableProp: Int by Delegates.vetoable(0){
_, oldValue, newValue ->
// 如果新的值大于旧值,则生效
newValue > oldValue
}
PreferencesUtil.getInstance().putBoolean(XXXXX, false);
下面通过委托,我们可以将一个普通属性的读写进行代理,代理到通过SP读写,这样我们在代码中对这个属性的读写,实际上是将其代理到SP中,代码如下所示。
@Suppress("UNCHECKED_CAST")
class PreferenceDelegate<T>(private val context: Context, private val propName: String, private val defaultValue: T) : ReadWriteProperty<Any, T> {
private val sharedPreferences: SharedPreferences by lazy { context.getSharedPreferences("SP_NAME", Context.MODE_PRIVATE) }
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
value?.let { putSPValue(propName, value) }
}
override fun getValue(thisRef: Any, property: KProperty<*>): T {
return getSPValue(propName, defaultValue) ?: defaultValue
}
private fun <T> getSPValue(name: String, defaultValue: T): T? = with(sharedPreferences) {
val result = when (defaultValue) {
is String -> getString(name, defaultValue)
is Int -> getInt(name, defaultValue)
is Long -> getLong(name, defaultValue)
is Float -> getFloat(name, defaultValue)
is Boolean -> getBoolean(name, defaultValue)
else -> null
}
result as T
}
private fun <T> putSPValue(name: String, value: T) = with(sharedPreferences.edit()) {
when (value) {
is Long -> putLong(name, value)
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
else -> null
}
}?.apply()
}
使用:
var valueInSP: String by PreferenceDelegate(this, "test", "init")
Log.d("xys", valueInSP)
valueInSP = "new value"
Log.d("xys", valueInSP)
out:
D/xys: init
D/xys: new value
通过上面的操作,我们在使用SharedPreferences的时候,只需要对某个要操作的属性使用by进行标记,将其委托给PreferenceDelegate即可,这样表面上好像是在操作一个String,但实际上,已经是对SharedPreferences的操作了。
在下面这个lib中,对很多场景下的委托进行了封装,大家可以参考下它的实现。
https://github.com/fengzhizi715/SAF-Object-Delegate
向大家推荐下我的网站 https://xuyisheng.top/ 点击原文一键直达
专注 Android-Kotlin-Flutter 欢迎大家访问
我安装了ruby、yeoman,当我运行我的项目时,出现了这个错误:Warning:Running"compass:dist"(compass)taskWarning:YouneedtohaveRubyandCompassinstalledthistasktowork.Moreinfo:https://github.com/gruUse--forcetocontinue.Use--forcetocontinue.我有进入可变session目标的路径,但它不起作用。谁能帮帮我? 最佳答案 我必须运行这个:geminstallcom
文章目录一、项目场景二、基本模块原理与调试方法分析——信源部分:三、信号处理部分和显示部分:四、基本的通信链路搭建:四、特殊模块:interpretedMATLABfunction:五、总结和坑点提醒一、项目场景 最近一个任务是使用simulink搭建一个MIMO串扰消除的链路,并用实际收到的数据进行测试,在搭建的过程中也遇到了不少的问题(当然这比vivado里面的debug好不知道多少倍)。准备趁着这个机会,先以一个很基本的通信链路对simulink基础和相关的debug方法进行总结。 在本篇中,主要记录simulink的基本原理和基本的SISO通信传输链路(QPSK方式),计划在下篇记
我不是Ruby专家,但想弄清楚发生了什么,因为我试图让指南针在节点应用程序中工作,但我的Ruby似乎坏了。打字:ruby--version让我:ruby2.1.1p76(2014-02-24revision45161)[x86_64-darwin13.0]我安装了Homebrew,之前遇到过Ruby版本的问题,但它似乎已安装并且可以正常工作。但是,当我使用gem输入请求时,出现此错误:$gem-hErrorloadingRubyGemsplugin"/Users/user_dir/.rvm/gems/ruby-2.1.1@global/gems/executable-hooks-1.3
我正在尝试安装bootstrap-sass并收到以下错误。我试过旧版本的sass,但bundler一直在安装3.3.0。WARN:UnresolvedspecsduringGem::Specification.reset:sass(~>3.2)WARN:Clearingoutunresolvedspecs.Pleasereportabugifthiscausesproblems./Library/Ruby/Gems/2.0.0/gems/compass-0.12.2/lib/compass/sass_extensions/monkey_patches/browser_support.r
目录配置模拟模拟类型与实例期望录制-回放-验证指定调用计数验证指定自定义结果验证调用参数联级模拟部分模拟模拟未实现的类其他伪装伪装方法及类伪装未实现类本文主要内容如何在SpringBoot中配置使用JMockit如何mock/faking依赖的对象如何对行为mock如何VerificationJMockit之所以强大,是因其使用了javaagent对类的字节码做了修改,在JVM的所有mock工具中,它是功能最强大的。同时注解又是最少的。配置在SpringBoot项目中使用JMockit隔离代码做单元测试,需要做以下配置引入JMockit依赖。dependencies>dependency>gr
几年前,我从一些Rails初学者指南开始学习Ruby/Rails。那时我已经学习了Rails的基础知识,例如模型和路由的一些约定优于配置,以及如何使用helpers等。但是,我并没有坚持多久,因为此后不久我发现了Sinatra,并决定我个人更喜欢它。不过,我最终真的爱上了Ruby,从那以后我写了很多Ruby,几乎没有一个是针对任何Rails项目的。然而,事实证明大部分可用的Ruby工作都是针对Rails应用程序的。所以我现在想再尝试一下Rails。现在,该引用资料很棒并且有很多有用的信息,但我只查看了我需要的特定内容的引用资料,而没有记住。但我不太可能在引用资料中看到像script/c
我正在研究rubyonrails指南,即http://guides.rubyonrails.org/layouts_and_rendering.html上的“布局和渲染”主题我对将实例变量传递给redirect_to方法感到困惑。这怎么可能?我认为redirect_to与重定向到另一个网页或url相关。在指南中给出的示例中,它说了以下内容:2.2.2RenderinganAction’sViewIfyouwanttorendertheviewthatcorrespondstoadifferentactionwithinthesametemplate,youcanuserenderw
文/高扬(微信公众号:量子论)据上次3月18号发布的V1.8版,已经过去十天,这期间AI领域发生了很多重大变化。因此,我们对《ChatGPT实用指南》进行了重大改版,增加了大量实用的操作和详细的讲解,保证小白可以轻松上手,快速驾驭ChatGPT。V2.0版本亮点:1、结构更合理。分为基础篇、进阶篇、高级篇,从易到难,由浅入深,符合学习规律。2、内容更充实。扩充了27页的内容,尽量看图说话,将操作步骤一步步地展示出来。3、排版更美观。按图书出版的规范制作,便于知识点查阅。后记:2022年11月底,我们在HackerNews上看到了关于ChatGPT的新闻报道后,开始意识到,人工智能的春天来了,这
2021年,游戏圈上演了一场精彩绝伦的抢人大战。在上海游戏圈,年薪百万的人越来越多了。据多名HR估算,在上海,过去一年TA、引擎、美术等稀缺岗位拟的薪资涨幅大概在20%-30%左右。某位圈内知名资深游戏猎头对此发出感叹:“50K的数值策划、角色原画;70K的技术美术;80K的技术总监...他们的年薪总包都接近百万,就连应届生入行的薪资也水涨船高,这要是放在以往都是不敢想象的”。以往含年薪、期权等的年总包收入上百万元,起码得是总监级别。如今工作五六年的人从广深跳到上海游戏公司,年薪能从50-70万跃上100万元,拿百万年薪的游戏从业者越来越多了上海游戏圈近年发展迅速,既有颇具发展潜力的中生代F4
前言本文隶属于专栏《大数据技术体系》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢!本专栏目录结构和参考文献请见大数据技术体系MySQL与StarRocks建表区别StarRocks兼容MySQL5协议,在建表时,与MySQL稍有不同。MySQL中建表语句CREATETABLE mysqltestdb・test_mysql(dateid DATE,siteid INTDEFAULT10,citycode SMALLINT,username VARCHAR(32)DEFAULT'',pv BIGINTDEFAULT0)ENGINE=InnoDBDEFAULTCHARS