I have a grails 2.2.4 app with a domain class Monster
:
class Monster {
int aggression
}
I can mock and test it like so:
import spock.lang.*
class MonsterSpec extends Specification {
def "property mocks work"() {
given:
def m = Mock(Monster)
m.aggression >> 5
expect:
m.aggression == 10
}
}
Recently I decided to give it an abstract base class ( not a domain object itself) so that I could share method implementations among my many Monster
-like classes:
abstract class Entity {} // Not under /domain
class RefactoredMonster extends Entity {
int aggression
}
But then a thousand simple tests all broke, like so:
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!
}
}
What on Earth is going on? The problem goes away if I make Entity
concrete, but then of course I can't hydrate any Monster
objects since Hibernate doesn't know what to do with Entity
(and I don't want to make Entity
a domain object, though I suppose I will if I really must).
What am I missing?
The problem is GORM expects the super class to be a domain class.
With Groovy 2.0, which is what Grails 2.2.4 has, you can use compile-time mixins to add methods to a class. This allows for method reuse without inheritance.
The Entity
can remain as not a domain class, but it must be a concrete class. Then, instead of subclassing, use it as a mixin.
@Mixin(Entity)
class RefactoredMonster {
int aggression
}
Since you need the ability to override methods, as you said, Mixins are out.
Looking at this from a higher level, a potential issue is the architecture/design. Inheritance is meant to represent is-a relationships (ex. a Dog is an Animal). But when inheritance is used primarily as a way to reuse methods, it can lead to... a mess.
It may be better to forgo inheritance and choose has-a (delegation) instead. This would allow you to reuse behaviour and override it when needed. Unfortunately, Groovy 2.0 doesn't support @Delegate. So the following example is going to have more boilerplate code than the same thing coded in Groovy 2.4 would:
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"
In the example above RealDuck
and RubberDuck
represent domain classes (which is why I'm not injecting the flier). The flying behaviour is demanded by an interface and implemented either via a class which only implements the behaviour ( FlierImp
), or by implementing it directly, as shown in RubberDuck
.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.