22. Groovy 面向对象编程-Traits特性学习-第三篇 super关键字

1. 介绍

本篇为Groovy学习第22篇内容,继续接着学习traits相关知识。

前面介绍了如何创建traits,如何使用traits。如何使用多个trait以及继承和运行时状态下的traits使用

本篇接着上篇https://zinyan.com/?p=447 未完成的知识点继续学习。

PS:traits 翻译为特征,或者特性等。

2. 链接行为

Groovy支持可堆叠traits的概念。如果当前traits无法处理消息,则将一个traits委托给另一个traits。

示例如下:

代码语言:javascript
复制
//创建一个消息接口对象
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,让它的职责仅限于日志记录。示例如下:

代码语言:javascript
复制
trait LoggingHandler implements MessageHandler {
void on(String message, Map payload) {
println "看到消息: message 内容为: payload"
super.on(message, payload)
}
}

然后,重构一个消息处理类:

代码语言:javascript
复制
class HandlerWithLogger implements DefaultHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])

执行run之后,将会输出:

代码语言:javascript
复制
看到消息: 这是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的内容都进行输出。同时保留状态。

这样就可以实现,专门的日志记录但是又不影响整个业务的流程。

如果不太能理解上面的转换,那么我们在中间添加一个拦截状态:

代码语言:javascript
复制
//专门处理消息开头为 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特性:

代码语言:javascript
复制
class HandlerWithLogger implements DefaultHandler,SayHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
loggingHandler.on('say: 测试一下数据', [name:'zinyan'])

输出内容为:

代码语言:javascript
复制
看到消息: 这是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后面的继承顺序也有比较重要的影响。例如我们更换顺序,输出内容就会有比较大的差别了:

代码语言:javascript
复制
class HandlerWithLogger implements SayHandler,DefaultHandler, LoggingHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
loggingHandler.on('say: 测试一下数据', [name:'zinyan'])

输出结果为:

代码语言:javascript
复制
看到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
收到消息: 这是Z同学网站 内容为: [url:https://zinyan.com, name:zinyan]
看到消息: say: 测试一下数数据 内容为: [name:zinyan]
收到消息: say: 测试一下数数据 内容为: [name:zinyan]

可以明显看到,处理逻辑根本没有进入到SayHandler中就结束了。如果再更换一下:

代码语言:javascript
复制
class HandlerWithLogger implements DefaultHandler, LoggingHandler,SayHandler {}
def loggingHandler = new HandlerWithLogger()
loggingHandler.on('这是Z同学网站', [url:'https://zinyan.com',name:'zinyan'])
loggingHandler.on('say: 测试一下数据', [name:'zinyan'])

输出内容为:

代码语言:javascript
复制
看到消息: 这是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)的超级类处理。

示例如下:

代码语言:javascript
复制
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()

将会输出:

代码语言:javascript
复制
zinyan
这里已经到底了
https://zinyan

可以看到,trait中的super方法将会由实体类的父类进行处理了。那如果没有父类会怎么样?例如:

代码语言:javascript
复制
class Demo  implements zin,yan{}
def demo = new Demo()
demo.text()

将会输出MissingMethodException错误异常。

代码语言:javascript
复制
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: []

我们除了可以使用在自定义类中。我们也可以将这个特性用在基本数据类型的方法重构中。示例如下:

代码语言:javascript
复制
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

如果觉得介绍的还可以,希望能够给我点个赞,鼓励一下。谢谢