在某些情况下,如果不受控制的类具有其他方法,它会很有用。为了启用此功能,Groovy 实现了一个从 Objective-C 借用的功能,称为Categories。
类别是用所谓的类别类实现的。类别类的特殊之处在于它需要满足某些用于定义扩展方法的预定义规则。
系统中包含一些类别,用于向类添加功能,使它们在 Groovy 环境中更有用:
默认情况下不启用类别类。要使用类别类中定义的方法,必须应用GDK 提供的作用域use
方法,该方法可从每个 Groovy 对象实例内部获得:
use(TimeCategory) {
// TimeCategory添加方法到Integer
println 1.minute.from.now
println 10.hours.ago
// TimeCategory添加方法到Date
def someDate = new Date()
println someDate - 3.months
}
该use
方法将类别类作为其第一个参数,将闭包代码块作为第二个参数。里面对 Closure
类方法的访问是可用的。从上面的示例中可以看出,甚至 JDK 类java.lang.Integer
和java.util.Date
也可以使用用户定义的方法来丰富或丰富。
一个类别不需要直接暴露给用户代码,以下也可以:
class JPACategory{
// 让我们在不进入JSRcommittee的情况下增强JPA EntityManager
static void persistAll(EntityManager em , Object[] entities) {
//添加一个saveAll的接口
entities?.each { em.persist(it) }
}
}
def transactionContext = {
EntityManager em, Closure c ->
def tx = em.transaction
try {
tx.begin()
use(JPACategory) {
c()
}
tx.commit()
} catch (e) {
tx.rollback()
} finally {
// 在此处清理资源
}
}
// 用户代码中,他们总是在异常情况下忘记关闭资源,有些甚至忘记提交,让我们不要依赖他们。
EntityManager em; //可能已注入
transactionContext (em) {
em.persistAll(obj1, obj2, obj3)
// 让我们在这里进行一些逻辑分析,使示例变得合理
em.persistAll(obj2, obj4, obj6)
}
当我们查看groovy.time.TimeCategory
类时,我们看到扩展方法都声明为static
方法。事实上,这是类别类必须满足的要求之一,才能将其方法成功添加到use
代码块内的类中:
public class TimeCategory {
public static Date plus(final Date date, final BaseDuration duration) {
return duration.plus(date);
}
public static Date minus(final Date date, final BaseDuration duration) {
final Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.YEAR, -duration.getYears());
cal.add(Calendar.MONTH, -duration.getMonths());
cal.add(Calendar.DAY_OF_YEAR, -duration.getDays());
cal.add(Calendar.HOUR_OF_DAY, -duration.getHours());
cal.add(Calendar.MINUTE, -duration.getMinutes());
cal.add(Calendar.SECOND, -duration.getSeconds());
cal.add(Calendar.MILLISECOND, -duration.getMillis());
return cal.getTime();
}
// ...
另一个要求是静态方法的第一个参数必须定义方法一旦被激活就附加到的类型。其他参数是该方法将作为参数的普通参数。
由于参数和静态方法的约定,类别方法定义可能不如普通方法定义直观。作为替代方案,Groovy 带有一个@Category
注解,它在编译时将注解的类转换为类别类。
class Distance {
def number
String toString() { "${number}m" }
}
@Category(Number)
class NumberCategory {
Distance getMeters() {
new Distance(number: this)
}
}
use (NumberCategory) {
assert 42.meters.toString() == '42m'
}
应用@Category
注解的优点是能够使用实例方法,而无需将目标类型作为第一个参数。目标类型类作为注解的参数给出。
:::info
@Category
在编译时元编程部分中有一个不同的部分。
:::