1. 介绍
本篇为Groovy学习第22篇内容,继续接着学习traits相关知识。
前面介绍了如何创建traits,如何使用traits。如何使用多个trait以及继承和运行时状态下的traits使用
本篇接着上篇https://zinyan.com/?p=447 未完成的知识点继续学习。
PS:traits 翻译为特征,或者特性等。
2. 链接行为
Groovy支持可堆叠traits的概念。如果当前traits无法处理消息,则将一个traits委托给另一个traits。
示例如下:
//创建一个消息接口对象 interface MessageHandler { void on(String message, Map payload) }
//创建一个trait处理收到的消息
trait DefaultHandler implements MessageHandler {
void on(String message, Map payload) {
println "收到消息: message 内容为: payload"
}
}
//创建一个类,来继承trait
class SimpleHandlerWithLogging implements DefaultHandler {
void on(String message, Map payload) {
println "看到消息: message 内容为: payload" //打印收到的信息
DefaultHandler.super.on(message, payload) //调用super将发方法委托给父类处理
}
}
上面的示例可以运行,但是有以下两个缺陷:
- 日志记录逻辑绑定到“具体”处理程序。
- 我们在on方法中有一个对
DefaultHandler
的显式引用,这意味着如果我们碰巧更改了类实现的特性,代码将被破坏。
解决方法,可以编写另外一个traits,让它的职责仅限于日志记录。示例如下:
trait LoggingHandler implements MessageHandler {
void on(String message, Map payload) {
println "看到消息: message 内容为: payload"
super.on(message, payload)
}
}
然后,重构一个消息处理类:
class HandlerWithLogger implements DefaultHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
执行run之后,将会输出:
看到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
收到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
在上一篇学习了解过,如果两个trait中方法冲突后,implements中最右边的优先级最高,也就是说LoggerHandler方法将会覆盖掉DefaultHandler中的方法。但是在LoggingHandler中调用了super.on
方法。也就是说会调用上一级traits进行处理。
而在这里上一级traits就是DefaultHandler对象。所以输出结果会将两个trait的内容都进行输出。同时保留状态。
这样就可以实现,专门的日志记录但是又不影响整个业务的流程。
如果不太能理解上面的转换,那么我们在中间添加一个拦截状态:
//专门处理消息开头为 say字段的数据
trait SayHandler implements MessageHandler {
void on(String message, Map payload) {
if (message.startsWith("say")) {
println "I say ${message - 'say'}!"
} else {
super.on(message, payload)
}
}
}
添加SayHandler特性:
class HandlerWithLogger implements DefaultHandler,SayHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
loggingHandler.on('say: 测试一下数据', [name:'zinyan'])
输出内容为:
看到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
收到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
看到消息: say: 测试一下数数据 内容为: [name:zinyan]
I say : 测试一下数数据!
在第一条消息时,内容没有say字段开头。所以SayHandler没有做任何事务处理,直接抛弃给上一级也就是DefaultHandler进行处理。
而第二条消息时,内容有say字段,那么SayHandler直接处理了。所以DefaultHandler就没有收到消息进行处理了。
这种处理逻辑就是链接行为了。很明显能够链接是依靠了方法中的super
关键字。
同时impleaents后面的继承顺序也有比较重要的影响。例如我们更换顺序,输出内容就会有比较大的差别了:
class HandlerWithLogger implements SayHandler,DefaultHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
loggingHandler.on('say: 测试一下数据', [name:'zinyan'])
输出结果为:
看到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
收到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
看到消息: say: 测试一下数数据 内容为: [name:zinyan]
收到消息: say: 测试一下数数据 内容为: [name:zinyan]
可以明显看到,处理逻辑根本没有进入到SayHandler中就结束了。如果再更换一下:
class HandlerWithLogger implements DefaultHandler, LoggingHandler,SayHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
loggingHandler.on('say: 测试一下数据', [name:'zinyan'])
输出内容为:
看到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
收到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
I say : 测试一下数数据!
很明显当消息不满足SayHandler的处理逻辑时,会传递给后面的traits进行处理。而当它能够处理的时候,就直接进行处理了。
PS:所以,在Groovy中使用traits的时候不要当做java中的接口进行处理。它的先后顺序可能产生的结果也将天差地别。
2.1 内部特征的super使用
如果一个类实现了多个traits,并且实现了super
的调用,那么:
- 如果类实现了另一个特性,则调用将委托给链中的下一个特性。(上面的示例体现了这个关系)
- 如果链中没有任何特性,则super引用实现类(this)的超级类处理。
示例如下:
trait zin{
void text(){
println("zinyan")
super.text() //创建一个超类调用
}
}trait yan{
void url(){
println("https://zinyan")
}
}
class DemoSuper{
void text(){
println("这里已经到底了")
}
}class Demo extends DemoSuper implements zin,yan{}
def demo = new Demo()
demo.text()
demo.url()
将会输出:
zinyan
这里已经到底了
https://zinyan
可以看到,trait中的super方法将会由实体类的父类进行处理了。那如果没有父类会怎么样?例如:
class Demo implements zin,yan{}
def demo = new Demo()
demo.text()
将会输出MissingMethodException
错误异常。
Caught: groovy.lang.MissingMethodException: No signature of method: java.lang.Object.text() is applicable for argument types: () values: []
Possible solutions: getAt(java.lang.String), wait(), wait(long), tap(groovy.lang.Closure), wait(long, int), any()
groovy.lang.MissingMethodException: No signature of method: java.lang.Object.text() is applicable for argument types: () values: []
我们除了可以使用在自定义类中。我们也可以将这个特性用在基本数据类型的方法重构中。示例如下:
trait Filtering { //定义了一个trait将
StringBuilder append(String str) {
def subst = str.replace('o','') //将字符串中的o 替换为空格
super.append(subst)
}
String toString() { super.toString() }
}
//将一个StringBuilder方法进行运行时拼接
def sb = new StringBuilder().withTraits Filtering
sb.append('Groovy')
println(sb) //输出:Grvy
在上一章:https://zinyan.com/?p=447 介绍过 withTraits
关键字。
我们可以通过这种方式,在不对源码进行修改的前提下。实现我们自己的扩展需求。
3. 小结
本篇主要介绍了关于super关键字在traits中的使用。我们可以通过它实现和扩展很多类的使用场景和边界。
相关示例可以参考Groovy官方文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_chaining_behavior
如果觉得介绍的还可以,希望能够给我点个赞,鼓励一下。谢谢