1. 概述

在本教程中,我们将深入探讨 Groovy 中的 Trait 特性。该特性是在 Groovy 2.3 版本中引入的。

2. 什么是 Trait?

Trait 是一种可重用的组件,它封装了一组方法或行为,我们可以用它来扩展多个类的功能。

Trait 可以看作是“带实现和状态的接口”。所有 Trait 都使用 trait 关键字定义。

3. 方法

在 Trait 中声明方法的方式与在类中声明普通方法类似。不过,Trait 不支持 protected 或包私有(package-private)方法

我们来看一下公共和私有方法是如何实现的。

3.1. 公共方法

首先,我们来看看如何在 Trait 中实现一个公共方法。

创建一个名为 UserTrait 的 Trait,并添加一个 sayHello 公共方法:

trait UserTrait {
    String sayHello() {
        return "Hello!"
    }
}

接着创建一个实现该 Trait 的 Employee 类:

class Employee implements UserTrait {}

然后编写一个测试,验证 Employee 实例是否能调用 UserTrait 提供的 sayHello 方法:

def 'Should return msg string when using Employee.sayHello method provided by UserTrait'() {
    when:
    def msg = employee.sayHello()

    then:
    msg
    msg instanceof String
    msg == "Hello!"
}

3.2. 私有方法

我们也可以在 Trait 中定义私有方法,并在其他公共方法中调用它。

来看一下 UserTrait 中的实现:

private String greetingMessage() {
    return 'Hello, from a private method!'
}
    
String greet() {
    def msg = greetingMessage()
    println msg
    msg
}

⚠️ 注意:如果尝试在实现类中调用这个私有方法,会抛出 MissingMethodException 异常:

def 'Should return MissingMethodException when using Employee.greetingMessage method'() {
    when:
    employee.greetingMessage()

    then:
    thrown(MissingMethodException)
    specificationContext.thrownException.message ==
            "No signature of method: com.baeldung.traits.Employee.greetingMessage() is applicable for argument types: () values: []"
}

在 Trait 中,私有方法通常用于封装一些不希望被外部覆盖但又需要被其他公共方法使用的逻辑。

3.3. 抽象方法

Trait 也可以包含抽象方法,这些方法需要在实现类中提供具体实现:

trait UserTrait {
    abstract String name()
    
    String showName() {
       return "Hello, ${name()}!"
    }
}
class Employee implements UserTrait {
    String name() {
        return 'Bob'
    }
}

3.4. 覆盖默认方法

通常,Trait 会为它的公共方法提供默认实现,但我们可以在实现类中覆盖这些方法:

trait SpeakingTrait {
    String speak() {
        return "Speaking!!"
    }
}
class Dog implements SpeakingTrait {
    String speak() {
        return "Bow Bow!!"
    }
}

Trait 不支持 protectedprivate 访问修饰符

4. this 关键字

Trait 中的 this 关键字行为与 Java 类似。我们可以把 Trait 看作是父类。

例如,在 Trait 中定义一个返回 this 的方法:

trait UserTrait {
    def self() {
        return this 
    }
}

5. 接口

Trait 可以像普通类一样实现接口。

先定义一个接口:

interface Human {
    String lastName()
}

然后在 Trait 中实现这个接口:

trait UserTrait implements Human {
    String showLastName() {
        return "Hello, ${lastName()}!"
    }
}

最后在实现类中提供接口的抽象方法实现:

class Employee implements UserTrait {
    String lastName() {
        return "Marley"
    }
}

6. 属性

我们可以在 Trait 中像在类中一样添加属性:

trait UserTrait implements Human { 
    String email
    String address
}

7. 扩展 Trait

与 Groovy 类类似,Trait 也可以通过 extends 关键字继承另一个 Trait:

trait WheelTrait {
    int noOfWheels
}

trait VehicleTrait extends WheelTrait {
    String showWheels() {
        return "Num of Wheels $noOfWheels" 
    } 
}

class Car implements VehicleTrait {}

也可以通过 implements 子句实现多个 Trait:

trait AddressTrait {                                      
    String residentialAddress
}

trait EmailTrait {                                    
    String email
}

trait Person implements AddressTrait, EmailTrait {}

8. 多继承冲突

当一个类实现多个 Trait,并且这些 Trait 中有同名方法时,就会出现方法冲突。我们来看看 Groovy 是如何默认处理这种冲突的,以及如何手动解决。

8.1. 默认冲突解决机制

默认情况下,最后声明的 Trait 中的方法会被采用。

这样,Trait 帮助我们避免了经典的 菱形继承问题(Diamond Problem)

先创建两个有同名方法的 Trait:

trait WalkingTrait {
    String basicAbility() {
        return "Walking!!"
    }
}

trait SpeakingTrait {
    String basicAbility() {
        return "Speaking!!"
    }
}

然后创建一个类同时实现这两个 Trait:

class Dog implements WalkingTrait, SpeakingTrait {}

由于 SpeakingTrait 是最后声明的,因此 Dog 类默认会使用它的 basicAbility 实现。

8.2. 显式解决冲突

如果我们不想使用默认的冲突解决机制,可以通过 Trait.super.method() 显式指定调用哪个 Trait 的方法。

例如,我们给两个 Trait 添加一个同名方法:

String speakAndWalk() {
    return "Walk and speak!!"
}
String speakAndWalk() {
    return "Speak and walk!!"
}

然后在 Dog 类中显式指定调用 WalkingTrait 的方法:

class Dog implements WalkingTrait, SpeakingTrait {
    String speakAndWalk() {
        WalkingTrait.super.speakAndWalk()
    }
}

9. 运行时动态实现 Trait

我们可以使用 as 关键字在运行时将对象强制转换为某个 Trait:

trait AnimalTrait {
    String basicBehavior() {
        return "Animalistic!!"
    }
}

如果要同时实现多个 Trait,可以使用 withTraits 方法代替 as

def dog = new Dog()
def dogWithTrait = dog.withTraits SpeakingTrait, WalkingTrait, AnimalTrait

10. 总结

在本教程中,我们学习了如何在 Groovy 中创建和使用 Trait,并了解了它的一些强大特性。

✅ Trait 是一种非常有效的方式来为多个类提供通用实现和功能,它有助于减少代码重复,提升代码的可维护性。

如需查看本文的代码示例和单元测试,可以访问 GitHub 仓库


原始标题:An Introduction to Traits in Groovy