I have found a solution to this using groovy's mixin functionality. mixin allows a class to inherit the methods of another class, and also use the classes variables at runtime.
Using the above example, my solution looks like this:
@Mixin(printHello)
class sayHello {
@When('^I say hello$')
def iSayHello() {
response = "hello"
}
}
class printHello {
static def response
@Then('^Hello should be printed$')
def iPrintHello() {
assert response == "hello"
}
}
This allows me to generically set the response object from any class, and then make an assertion on it in the common steps file.