@Category与@Mixin
如果使用Groovy有一段时间了,那么你肯定知道Category这个概念。它是继承现有类型(甚至可以继承JDK中的final类以及第三方类库)并增加方法的一种机制。该技术还可用来编写领域特定语言。先来看看下面这个示例:
2
3 def number
4
5 String toString() { "${number}m" }
6
7 }
8
9 class NumberCategory {
10
11 static Distance getMeters(Number self) {
12
13 new Distance(number: self)
14
15 }
16
17 }
18
19 use(NumberCategory) {
20
21 def dist = 300.meters
22
23 assert dist instanceof Distance
24
25 assert dist.toString() == "300m"
26
27 }
这里我们假想了一个简单的Distance类,它可能来自于第三方,他们将该类声明为final以防止其他人继承该类。但借助于Groovy Category,我们可以用额外的方法装饰Distance类。这里我们通过装饰Number类型为number增加一个getMeters()方法。通过为number增加一个getter,你可以使用Groovy优雅的属性语法来引用number。这样就不必使用300.getMeters()了,只需使用300.meters即可。
这种Category系统和记号不利的一面是:要想给其它类型增加实例方法,我们需要创建static方法,而且该方法的第一个参数代表了我们要影响的类型。其他参数就是方法所使用的一般参数了。因此这与加到Distance中的一般方法相比不那么直观,我们是否应该访问源代码以对其增强。现在就是@Category注解发挥作用的时候了,它会将拥有(要增加的)实例方法的类转换为Groovy category:
2
3 class NumberCategory {
4
5 Distance getMeters() {
6
7 new Distance(number: this)
8
9 }
10
11 }
无需将方法声明为static的,同时这里所用的this实际上是category所操纵的number而不是我们所创建的category实例的this。然后可以继续使用use(Category) {}构造方法来应用category。这里需要注意的是这些category一次只能用在一种类型上,这与传统的category不同(可以应用到多种类型上)。
现在通过搭配@Category与@Mixin,我们可以将多种行为混合到一个类中,类似于多继承:
2
3 def fly() { "I'm the ${name} and I fly!" }
4
5 }
6
7 @Category(Vehicle) class DivingAbility {
8
9 def dive() { "I'm the ${name} and I dive!" }
10
11 }
12
13 interface Vehicle {
14
15 String getName()
16
17 }
18
19 @Mixin(DivingAbility)
20
21 class Submarine implements Vehicle {
22
23 String getName() { "Yellow Submarine" }
24
25 }
26
27 @Mixin(FlyingAbility)
28
29 class Plane implements Vehicle {
30
31 String getName() { "Concorde" }
32
33 }
34
35 @Mixin([DivingAbility, FlyingAbility])
36
37 class JamesBondVehicle implements Vehicle {
38
39 String getName() { "James Bond's vehicle" }
40
41 }
42
43 assert new Plane().fly() ==
44
45 "I'm the Concorde and I fly!"
46
47 assert new Submarine().dive() ==
48
49 "I'm the Yellow Submarine and I dive!"
50
51 assert new JamesBondVehicle().fly() ==
52
53 "I'm the James Bond's vehicle and I fly!"
54
55 assert new JamesBondVehicle().dive() ==
56
57 "I'm the James Bond's vehicle and I dive!"
58
我们并没有继承多个接口并将相同的行为注入到每个子类中,相反我们将category混合到类中。示例中James Bond(007)的交通工具通过掺元获得了飞翔和潜水的能力。
值得注意的是,不像@Delegate可以将接口注入到声明代理的类中,@Mixin是在运行时实现掺元——在后面的元编程增强一节中将会深入探讨这一点。