我有一个带有域类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
成为域名对象,但我想如果我真的必须这样做,我会这样做。
我错过了什么?
答案 0 :(得分:1)
问题是GORM希望超类成为域类。
使用Grails 2.2.4所具有的Groovy 2.0,您可以使用编译时mixins向类添加方法。这允许在没有继承的情况下重用方法。
Entity
可以保留为域类,但必须是具体类。然后,不要使用子类,而是将其用作mixin。
@Mixin(Entity)
class RefactoredMonster {
int aggression
}
由于你需要能够覆盖方法,正如你所说,Mixins已经出局了。
从更高层面来看,潜在的问题是架构/设计。继承意味着代表 is-a 关系(例如,狗是动物)。但是当继承主要用作重用方法的方法时,它可能导致......一团糟。
放弃继承并选择 has -a (委托)可能会更好。这将允许您重用行为并在需要时覆盖它。不幸的是,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
代表了域类(这就是我没有注入飞行器的原因)。通过接口要求飞行行为,并通过仅实现行为(FlierImp
)的类或直接实现它来实现,如RubberDuck
所示。< / p>