21. Groovy 面向对象编程-Traits特性学习-第二篇

2023-02-23 17:37:12 浏览数 (1)

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进行扩展。示例如下:

代码语言:javascript复制
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方法,如methodMissingpropertyMissing,在这种情况下,实现类将从trait继承行为,就像下面的例子:

代码语言:javascript复制
//创建一个名为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。示例如下:

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

代码语言:javascript复制
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进行转换并关联:

代码语言:javascript复制
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类型强制等等。

如果觉得本篇介绍内容还可以,希望能够点个赞鼓励一下。谢谢。

0 人点赞