1. 介绍
本篇内容为Groovy学习第二十一篇,上篇学习了Traits的一些使用。本篇继续深入学习Traits相关知识。
具体内容可以通过目录名称进行了解。这里就不做展开了。
在上一篇已经基本介绍了Traits是什么了,并且如何创建与使用。而本篇内容接着上篇内容没有讲完的知识点继续扩展。
2. 扩展特性
2.1 extends 扩展
trait之间是可以通过extends
关键字实现继承的。如果我们定义的trait需要另外一个trait的信息。那么使用extends就是一个不错的选择了。
示例如下:
代码语言:javascript复制//创建了一个trait对象
trait Named {
String name
}
//创建了一个Plite 并继承Named
trait Polite extends Named {
String introduce() { "嗨,我是 $name" }
}
class Person implements Polite {}
def p = new Person(name: 'zinyan')
println(p.introduce()) // 输出:嗨,我是 zinyan
2.2 implements 扩展
我们单对单可以使用extends
关键字。但是我们如果有多个trait想扩展集合呢?那么trait还支持使用implements
进行扩展。示例如下:
trait WithId {
Long id
}
trait WithName {
String name
}
//Identified 就集合了 另外两个trait对象的特性
trait Identified implements WithId, WithName {}
class Demo implements Identified{}
def zinyan = new Demo(id:1023,name:'zinyan.com')
println(zinyan.id) //输出 : 1023
println(zinyan.name) //输出:zinyan.com
3. Duck 类型 和Traits
我们知道duck typing 也叫做 鸭子类型。是动态类型的一种风格。它的定义是:
一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
简单理解就是:当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。
这也是叫做鸭子类型的原因。
Traits可以调用任何动态代码,就像普通的Groovy类一样。这意味着我们可以在方法体中调用应该存在于实现类中的方法,而不必在接口中显式地声明它们。这意味着特征与鸭子类型完全兼容,具体示例如下:
代码语言:javascript复制// 创建一个trait对象
trait SpeakingDuck {
String speak() { quack() } // 该方法 返回一个quack()方法
}
class Duck implements SpeakingDuck {
String methodMissing(String name, args) { //Dock通过methodMissing 实现了quack方法,将方法的name转为首字母大写后输出为String
"${name.capitalize()}!"
}
}
def d = new Duck()
//当我们调用speak()方法的时候,触发了quack方法的调用,而该方法会被methodMissing进行处理。
println(d.speak()) //输出 Quack!
trait也有可能实现MOP方法,如methodMissing
或propertyMissing
,在这种情况下,实现类将从trait继承行为,就像下面的例子:
//创建一个名为DynamicObject的trait对象
trait DynamicObject {
//定义了一个私有Map对象props
private Map props = [:]
//重构了methodMissing 方法,将name值转为大写
def methodMissing(String name, args) {
name.toUpperCase()
}
//重构了propertyMissing 方法,将数据从props数组中获取
def propertyMissing(String name) {
props.get(name)
}
//重构了setProperty方法,将数据添加到props数组中
void setProperty(String name, Object value) {
props.put(name, value)
}
def getPros(){
props
}
}
class Dynamic implements DynamicObject {
String existingProperty = 'ok' // 添加了一个属性值 ok
String existingMethod() { 'ok' } //添加了一个方法,返回值为:ok
}
def d = new Dynamic()
println(d.existingProperty) //输出: ok 因为属性已经存在了
println(d.foo) //输出null ,因为这个属性不存在。
//如果方法不存在,将会触发创建操作,也就是 methodMissing 方法。
d.foo = 'zinyan.com'
//这个时候已经创建成功了,所以会返回创建时添加的value值。
println(d.foo) //输出: zinyan.com
println(d.existingMethod()) //方法存在,所以直接输出: ok
// 因为zinyanUrl方法不存在,将会创建,然后将名称转为大写并打印,
println(d.zinyanUrl()) // 输出:ZINYANURL
println(d.getPros()) //把trait中的Map对象打印一下, 输出:[foo:zinyan.com]
3.1 MOP方法
如果你对这个不太了解,刚好可以学习一下。MOP其实就是方法拦截。在Java中通过AOP对方法进行拦截,而在Groovy就是通过MOP进行拦截。
可以在方法调用前,进行拦截并执行我们需要的操作,然后再返回。
在上面的示例中就是MOP拦截的一个使用了。
这里只是简单提醒一下,后面会专门分享关于MOP方法拦截的相关知识。
4. 多重继承冲突
一个类可以实现多个trait。如果某个trait定义的方法与另一个trait中的方法具有相同的命名,就会产生冲突。示例如下:
代码语言:javascript复制trait A {
String exec() { 'A' }
}
trait B {
String exec() { 'B' }
}
class C implements A,B {}
trait A 和 trait B都有一个exec()
方法。当Class C继承这两个trait的时候就出现了冲突。
而Groovy有一个默认冲突解决方案。发生冲突时,implements
子句中最后声明的trait的方法获胜。在这里,B声明在A之后,因此B中的方法将被提取,A方法将会被舍弃。我们执行输出时就会输出B。示例如下:
def c = new C()
println(c.exec()) //输出:B
4.1 手动解决冲突
默认情况下Groovy会按照声明顺序进行舍弃。那么还是上面的示例,我们如果想使用A的方法,而不是B的方法。又不想修改顺序。那么我们就需要手动处理了。示例如下:
代码语言:javascript复制class C implements A,B {
String exec() { A.super.exec() } //我们重构发生冲突的方法,定义为A.supper.exec()
}
def c = new C()
assert c.exec() == 'A'
我们可以主动告诉Groovy编译器,这个方法使用A的版本而不是使用B的版本。通过这种方式进行修改后。implements
后面的顺序将会失效。冲突将会按照我们指定的需求进行解决,用上面的示例来介绍就是,将会抛弃B方法。不管B方法是先继承还是后继承。
5. traits的运行时实现
Groovy还支持在运行时动态实现特性。允许我们使用traits“装饰”一个现有的对象。示例如下:
代码语言:javascript复制trait Extra {
String extra() { "这是一个额外的方法" }
}
class Something {
String doSomething() { '来自zinyan的某些方法' }
}
在创建过程中,它们两个互相没有关联,但是我们在执行的时候让它们结合例如:
代码语言:javascript复制def s = new Something()
println(s.extra)
我们直接执行,会输出MissingPropertyException
错误:
Caught: groovy.lang.MissingPropertyException: No such property: extra for class: Something
groovy.lang.MissingPropertyException: No such property: extra for class: Something
at DSJ.run(DSJ.groovy:8)
因为它们没有关联在一起,而我们可以使用as
进行转换并关联:
trait Extra {
String extra() { "这是一个额外的方法" }
}
class Something {
String doSomething() { '来自zinyan的某些方法' }
}
def s = new Something() as Extra
println(s.extra()) //输出:这是一个额外的方法
println(s.doSomething()) //输出 :来自zinyan的某些方法
我们都知道 as
关键字在Groovy中是起到强制转换作用的。
当强制转换一个对象到trait
的时候,将会创建一个新的实例对象,该对象将会实现原始对象的属性和接口的同时,扩展trait的属性和接口。
5.1 运行时多trait实现
上面的介绍,只是实现了一个trait。那么如果在运行的时候,希望实行多个trait汇合就需要使用关键字:withTraits
了。
示例如下:
代码语言:javascript复制trait A { String methodFromA() {'本站是:Z同学小站'} }
trait B { String methodFromB() { '链接是:https://zinyan.com'} }
class C{} //普通类,什么trait都没有继承
def xan = new C().withTraits A,B
println(xan.methodFromA()) //输出:本站是:Z同学小站
println(xan.methodFromB()) //输出:链接是:https://zinyan.com
当强制一个对象具有多个特征时,操作的结果不是同一个实例。可以保证强制对象将实现原始对象实现的特征和接口,但结果将不是原始类的实例。
小结:在运行时强制执行trait加载,会创建一个新的实例。并不是原先的实例集成traits哦。也就是说在是两个对象了。
6. 小结
本篇内容,可以参考Groovy官网文档:http://docs.groovy-lang.org/docs/groovy-4.0.6/html/documentation/#_extending_traits
学习了扩展,Duck类型,继承冲突以及运行时加载的特性。都是一些在实际使用中比较需要的功能。
下篇将会继续学习Traits的特性,例如行为链接,SAM类型强制等等。
如果觉得本篇介绍内容还可以,希望能够点个赞鼓励一下。谢谢。