我有一个带有域类 Monster
的 grails 2.2.4 应用程序:
class Monster {
int aggression
}
我可以像这样模拟和测试它:
import spock.lang.*
class MonsterSpec extends Specification {
def "property mocks work"() {
given:
def m = Mock(Monster)
m.aggression >> 5
expect:
m.aggression == 10
}
}
最近我决定给它一个抽象基类(而不是域对象本身),这样我就可以在我的许多
Monster
之间共享方法实现。 - 类:abstract class Entity {} // Not under /domain
class RefactoredMonster extends Entity {
int aggression
}
但是随后一千个简单的测试都失败了,就像这样:
import spock.lang.*
class MonsterSpec extends Specification {
def "property mocks work"() {
given:
def m = Mock(RefactoredMonster)
m.aggression >> 10
expect:
m.getAggression() == 10 // This works
and:
m.aggression == 10 // This fails! m.aggression is null!
}
}
地球上发生了什么?如果我制作
Entity
,问题就会消失具体的,但是当然我不能给任何东西补水Monster
对象,因为 Hibernate 不知道如何处理 Entity
(而且我不想让 Entity
成为域对象,尽管我想如果我真的必须这样做的话)。我错过了什么?
最佳答案
问题是 GORM 期望父类(super class)是域类。
使用 Grails 2.2.4 所具有的 Groovy 2.0,您可以使用编译时 mixins向类添加方法。这允许在没有继承的情况下重用方法。Entity
可以保留为不是域类,但它必须是具体类。然后,而不是子类化,将其用作 mixin。
@Mixin(Entity)
class RefactoredMonster {
int aggression
}
替代
正如您所说,由于您需要覆盖方法的能力,因此 Mixins 已出局。
从更高的层面来看,一个潜在的问题是架构/设计。继承旨在表示 is-a 关系(例如,狗是动物)。但是,当继承主要用作重用方法的一种方式时,它可能会导致……一团糟。
放弃继承并选择 has-a (委托(delegate))可能会更好。这将允许您重用行为并在需要时覆盖它。不幸的是,Groovy 2.0 不支持@Delegate。因此,以下示例将具有比 Groovy 2.4 中编码的相同内容更多的样板代码:
interface Flier {
def fly();
}
class FlierImp {
def fly() { "I'm fying! WOOT!" }
}
class RealDuck implements Flier {
def flier
RealDuck() {
flier = new FlierImp() // Purposely not using injection
}
def fly() {
flier.fly()
}
}
class RubberDuck implements Flier {
def fly() { "I don't fly" }
}
def duck = new RealDuck()
def rubberDuck = new RubberDuck()
assert duck.fly() == "I'm fying! WOOT!"
assert rubberDuck.fly() == "I don't fly"
在上面的示例中
RealDuck
和 RubberDuck
表示域类(这就是我不注入(inject)传单的原因)。飞行行为是由接口(interface)要求的,可以通过只实现该行为的类 (FlierImp
) 或直接实现,如 RubberDuck
所示。 .
关于Grails:如何使用非域抽象基类模拟域对象的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34313244/