侧边栏壁纸
  • 累计撰写 372 篇文章
  • 累计创建 60 个标签
  • 累计收到 109 条评论

目 录CONTENT

文章目录

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

Z同学
2022-11-22 / 0 评论 / 1 点赞 / 25 阅读 / 1,957 字
温馨提示:
本文最后更新于 2022-11-24,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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

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

1

评论区