开发者

How do you share common methods in different grails controllers?

开发者 https://www.devze.com 2023-01-25 03:24 出处:网络
Currently when I need to share a method like processParams(params) between different controllers, I use either inheri开发者_如何学Gotance or services.

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:

  1. 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.
  2. 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

0

精彩评论

暂无评论...
验证码 换一张
取 消