# delegate4cj **Repository Path**: coyamo/delegate4cj ## Basic Information - **Project Name**: delegate4cj - **Description**: 使用宏实现仓颉语言的类委托、属性委托。 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2024-08-13 - **Last Updated**: 2025-02-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

delegate4cj

## 介绍 一个基于宏的变量及属性委托库,可以方便快捷实现委托。 ## 特性 1. 变量委托:将`let`、`var`修饰的变量被转换为属性实现属性委托 2. 属性委托:将属性的实现细节委托给另一个对象处理 3. 内置委托实现:`notNone`、`observable`、`vetoable`、`lazy` 4. 委托DSL:支持使用`by`实现委托如`@Delegate(var varVar: String by delegate)` 5. todo:函数委托与类委托(规划中) ## 开发计划 [ ] 属性\变量委托 80% [ ] 函数委托 [ ] 类委托 ## 使用文档 ### 添加依赖 在项目的`cjpm.toml`文件中的`[dependencies]`下添加以下依赖: ``` [dependencies.delegate] git = "https://gitcode.com/Dacec/delegate4cj" branch = "main" output-type = "static" ``` ### 导入包 使用注解需要导入`delegate.macros.*` 导入这个会自动导入以下两个包 ```cj import std.reflect.TypeInfo import delegate.property.* ``` ### 实现被委托对象 在使用属性/变量委托前,需要定义实现被委托对象。 这里约定重载对象的`()`运算符实现数据的读写, 约定实现`operator func ()(property:DelegateProperty): T`来从被委托的对象获取数据, 约定使用`operator func ()(value: T, property:DelegateProperty): Unit`把数据传递给被委托的对象。 比如实现一个委托字符串读写的类: ```cj class StringDelegate { private var str:String = "默认委托字符串" public operator func ()(property:DelegateProperty): String { return str } public operator func ()(v:String, property:DelegateProperty) { str = v + "-set-delegate" } } ``` 为了规范约束,这里定义了两个接口来限定接口`ReadOnlyProperty`、`ReadWriteProperty` 对于只读的属性,可以实现以下接口: ```cj /** 标注只读类型属性、变量委托需要实现的函数 */ public interface ReadOnlyProperty { /** 委托读取的函数 */ operator func ()(property:DelegateProperty): T } ``` 对于可读写的类型可以实现以下接口 ```cj /** 标注读写类型属性、变量委托需要实现的函数 */ public interface ReadWriteProperty { /** 委托读取的函数 */ operator func ()(): T /** 委托写的函数 */ operator func ()(value: T, property:DelegateProperty): Unit } ``` 所以之前给出的例子可以写为: ```cj class StringDelegate <: ReadWriteProperty { private var str:String = "默认委托字符串" public operator override func ()(property:DelegateProperty): String { return str } public operator override func ()(v:String, property:DelegateProperty) { str = v + "-set-delegate" } } ``` ### DSL `delegate4cj`提供了一个简易的DSL实现委托,在属性、变量定义后直接使用自定义的`by`关键字加上被委托对象: ```cj @Delegate( var varVar: String by delegate ) ``` 当然以下写法也是ok的: ```cj @Delegate( mut prop p1: String by delegate ) @Delegate( mut prop p2: String {} by delegate ) @Delegate( mut prop p3: String { get(){ return "get"} set(v){} } by delegate ) ``` 除了使用DSL的方式还可以使用宏参数的方式实现委托,后面的例子都以宏参数的方式为例,但是都可以转换为DSL方式实现(扩展委托宏如`@Lazy`除外)。 ### 属性委托 使用`@Delegate`宏标注在属性上,把被委托对象作为参数传入即可,注意属性后面的`{}`可以省略,如果实现了`get`和`set`会被委托的对象替换,被替换的getter/setter会保存在`DelegateProperty`结构中。 ```cj class TestProp { @Delegate[StringDelegate()] public prop normalProp: String {} @Delegate[StringDelegate()] public mut prop mutProp: String {} @Delegate[StringDelegate()] public static prop normalStaticProp: String {} @Delegate[StringDelegate()] public static mut prop mutStaticProp: String {} @Delegate[StringDelegate()] mut prop prop2Option:?String { get(){ return "hello" } set(v){ temp2 = v } } } ``` ### 变量委托 使用`@Delegate`宏标注在变量上,把被委托对象作为参数传入即可,变量会被转换为属性以实现委托。注意如果这里设置了默认值,会被转换为getter保存在`DelegateProperty`结构中。 **变量委托的副作用**: 如果带有默认值类型不为值的语句如`let num:Int = Random().nextInt64()`,在不使用委托的情况下值是不会再变的,使用委托后转换的默认getter会在每次调用生成一个随机数。 ```cj class TestVar { @Delegate[StringDelegate()] public var normalVar: String @Delegate[StringDelegate()] public let letVar: String @Delegate[StringDelegate()] public static var normaStaticlVar: String @Delegate[StringDelegate()] public static let letStaticVar: String } ``` ### Map类型委托 对于标准库的`HashMap`、`ConcurrentHashMap`和`TreeMap`使用扩展实现了属性委托。其他类似数据结构都可以通过扩展实现委托。 限制:map中的key的名字要与类中的属性名字保持一致,且map的键类型须为字符串,值类型为属性的类型或更大范围的类型。 使用方法: ```cj class Test { let map = HashMap() init() { map.put("key", "key value") } @Delegate[map] var key:String } ``` ### 内置委托 #### notNone `notNone`委托实现确保委托的变量不能为空,可以用于延迟设置对象等场景。如果没有设置就获取会抛出`IllegalStateException`异常。 使用例子 ```cj class Demo { @Delegate[notNone()] var str: String } ``` 这里提供了一个简化调用的宏`@NotNone`,要求`@NotNone`在`@Delegate`上面且`@Delegate`不需要参数。 ```cj class Demo { @NotNone @Delegate var str: String } ``` #### observable `observable`委托实现对对象的监听,在值被设置之后触发回调。委托时需要指定初始值和实现回调。 ```cj class Demo { @Delegate[observable("hello") { property, oldValue, newValue => //... }] var observableStr: String } ``` 这里提供了一个简化调用的宏`@Observable`,要求`@Observable`在`@Delegate`上面且`@Delegate`不需要参数。`@Observable`接受一个无名的函数调用,传入初始值和回调。 ```cj class Demo { @Observable[("hello"){ property, oldValue, newValue => // ... }] @Delegate var observableStr: String } ``` #### vetoable `vetoable`委托实现对对象的监听,在值被设置之前触发回调,并且根据回调返回的`Bool`值确定是否拦截设置,如果防护`true`则表示允许设置。委托时需要指定初始值和实现回调。 ```cj class Demo { @Delegate[vetoable("hello") { property, oldValue, newValue => // ... true }] var vetoableStr: String } ``` 这里提供了一个简化调用的宏`@Vetoable`,要求`@Vetoable`在`@Delegate`上面且`@Delegate`不需要参数。`@Vetoable`接受一个无名的函数调用,传入初始值和回调。 ```cj class Demo { @Vetoable[("hello") { property, oldValue, newValue => // ... true }] @Delegate var vetoableStr: String } ``` #### lazy 用于实现懒加载,在实际调用的时候去初始化加载,且只加载一次 ```cj class LazyTest { let lazyVal = lazy { println("init") "hello" } @Delegate[lazyVal] let string:String } ``` 这里提供了`@Lazy`宏简化调用,要求`@Lazy`在`@Delegate`上面且`@Delegate`不需要参数。`@Lazy`接受一个lambda调用。 ```cj @Lazy[{ count++ "hello" }] @Delegate let stringMacro:String ``` ## 宏、注解冲突解决 因为是使用宏修改原有代码,所以如果同时使用其他宏或者注解可能会导致Token解析异常,需要明确宏的作用适当调整使用位置,可以兼容部分其他注解和宏。 ## 原理 使用宏替换掉,变量和属性的读写。变量会被转换为属性以实现委托。对于属性,为了保持`get`和`set`是使用的同一个委托对象,会创建一个以`_`开头的中间`private`对象。 ## License 本开源库受Kotlin标准库启发衍生开发,使用仓颉语言实现。 需要遵守Kotlin的[Apache License (Version 2.0)](http://www.apache.org/licenses/)开源协议 同时本开源库使用[MulanPSL2](http://license.coscl.org.cn/MulanPSL2)