How do you share common methods in different grails controllers?
Currently when I need to share a method like processParams(params)
between different controllers, I use either inheri开发者_如何学Gotance or services.
Both solution has some inconvenients :
- With inheritance, you cannot use multiple inheritance which means that you need to have all of your controller utility methods in one place. And also, there is a bug in grails that does not detect any code changes in Base Controller classes in development mode (you need to restart the app)
- With services, you don't have access to all injected properties like params, session, flush...
So my question is : is there any other way to use some common methods accessible for multiple controllers ?
One option I like is to write the common methods as a category, then mix it into the controllers as necessary. It gives a lot more flexibility than inheritance, has access to stuff like params, and the code is simple and understandable.
Here's a tiny example:
@Category(Object)
class MyControllerCategory {
def printParams() {
println params
}
}
@Mixin(MyControllerCategory)
class SomethingController {
def create = {
printParams()
...
}
def save = {
printParams()
}
}
Common functionality is a call for a new class, not necessarily common ancestor. The question formulation is missing responsibility statement for it. Needless to say, it's a single responsibility that we create a new class for. I take further decisions basing on class responsibility.
I prefer a hybrid of robbbert's and Jared's answers: I construct extra classes, passing them necessary controller internals as parameters. Sometimes the classes develop from method objects. Like:
def action = {
def doer = SomeResponsibilityDoer(this.request, this.response)
render doer.action()
}
Not so brief, but lets you get code under tests and keep coupling low.
As SomeResponsibilityDoer
is only going to have couple of fields - request an response - it's not a big deal constructing it with every request.
It's also not a big deal having SomeResponsibilityDoer
not reloaded on controller change in dev, because:
- Initially, you can declare it in some of Controller files - it will be reloaded. After you complete it, hopefully it won't change often, so move it to
src/groovy
. - Even more important, it's faster and better for design to develop under unit tests than under application running and reloading a Contoller.
This doesn't help the restarting in development mode issue you have, but it's the way I've solved this problem. It's ugly and probably not good practice, but I factor common code into classes as closures. Then I can do something like:
new ControllerClosures().action(this)
and from with in the controllerClosures class
def action={
it.response.something
return [allYourData]
}
You can use the Delegation design pattern:
class Swimmer {
def swim() { "swimming" }
}
class Runner {
def run() { "running" }
}
class Biker {
def bike() { "biking" }
}
class Triathlete {
@Delegate Swimmer swimmer
@Delegate Runner runner
@Delegate Biker biker
}
def triathlete = new Triathlete(
swimmer: new Swimmer(),
runner: new Runner(),
biker: new Biker()
)
triathlete.swim()
triathlete.run()
triathlete.bike()
In case of a controller, assign the helper class directly at the instance field (or in the nullary constructor):
class HelperClass {
def renderFoo() { render 'foo' }
}
class FooController {
private @Delegate HelperClass helperClass = new HelperClass()
def index = { this.renderFoo() }
}
The delegate's type information gets compiled into the containing class.
You can write all the common method in commonService and use that service to envoke commmon method
精彩评论