Using Groovy Closures Instead of Template Method

Java developers who need to build Domain-Specific Languages into their applications will find this book the passport to doing it with Groovy. Even the trickiest concepts are explained clearly and methodically.

 

Groovy for Domain-Specific Languages

Groovy for Domain-Specific Languages

Extend and enhance your Java applications with Domain Specific Languages in Groovy

  • Build your own Domain Specific Languages on top of Groovy
  • Integrate your existing Java applications using Groovy-based Domain Specific Languages (DSLs)
  • Develop a Groovy scripting interface to Twitter
  • A step-by-step guide to building Groovy-based Domain Specific Languages that run seamlessly in the Java environment
        Read more about this book      

(For more resources on Groovy, see here.)

Template Method Pattern Overview

The template method pattern often applies during the thought "Well I have a piece of code that I want to use again, but I can't use it 100%. I want to change a few lines to make it useful." In general, using this pattern involves creating an abstract class and varying its implementation through abstract hook methods. Subclasses implement these abstract hook methods to solve their specific problem.

This approach is very effective and is used extensively in frameworks. However, closures provide an elegant solution.

Sample HttpBuilder Request

It is best to illustrate the closure approach with an example. Recently I was developing a consumer of REST webservices with HttpBuilder. With HttpBuilder, the client simply creates the class and issues an HTTP call. The framework waits for a response and provides hooks for processing.

Many of the requests being made were very similar to one another, only the URI was different. In addition, each request needed to process the returned XML differently, as the XML received would vary. I wanted to use the same request code, but vary the XML processing. To summarize the problem:

  • HttpBuilder code should be reused
  • Different URIs should be sent out with the same HttpBuilder code
  • Different XML should be processed with the same HttpBuilder code

Here is my first draft of HttpBuilder code. Note the call to convertXmlToCompanyDomainObject(xml).

static String URI_PREFIX = '/someApp/restApi/'

private List issueHttpBuilderRequest(RequestObject requestObj, String uriPath) {
def http = new HTTPBuilder("http://localhost:8080/")
def parsedObjectsFromXml = []

http.request(Method.POST, ContentType.XML) { req ->
// set uri path on the delegate
uri.path = URI_PREFIX + uriPath
uri.query = [
company: requestObj.company,
date: requestObj.date
type: requestObj.type
]
headers.'User-Agent' = 'Mozilla/5.0'

// when response is a success, parse the gpath xml
response.success = { resp, xml ->
assert resp.statusLine.statusCode == 200
// store the list
parsedObjectsFromXml = convertXmlToCompanyDomainObject(xml)
}

// called only for a 404 (not found) status code:
response.'404' = { resp ->
log.info 'HTTP status code: 404 Not found'
}
}
parsedObjectsFromXml
}

private List convertXmlToCompanyDomainObject(GPathResult xml) {
def list = []
// .. implementation to parse the xml and turn into objects
}

As you can see, URI is passed as a parameter to issueHttpBuilderRequest. This solves the problem of sending different URIs, but what about parsing the different XML formats that are returned?

Using Template Method Pattern

The following diagram illustrates applying the template method pattern to this problem. In summary, we need to move the issueHttpBuilderRequest code to an abstract class, and provide an abstract method convertXmlToDomainObjects(). Subclasses would provide the appropriate XML conversion implementation.

Using Groovy Closures Instead of Template Method

 

        Read more about this book      

(For more resources on Groovy, see here.)

Using Closures

Now for the closure approach. Without creating extra classes or using a formal pattern, we can solve the same problem. We can define two closures in the same class, and pass these closures into the issueHttpBuilderRequest method. The method will execute the closure when appropriate. After all, we want to identify the area of variation and insert some different code at that variation point.

Notice the added argument Closure convertXmlToDomainObjects to the issueHttpBuilderRequest method, along with two new closure definitions.

static String URI_PREFIX = '/someApp/restApi/'

private List issueHttpBuilderRequest(RequestObject requestObj, String uriPath, Closure

convertXmlToDomainObjects) {
def http = new HTTPBuilder("http://localhost:8080/")
def parsedObjectsFromXml = []

http.request(Method.POST, ContentType.XML) { req ->
// set uri path on the delegate
uri.path = URI_PREFIX + uriPath
uri.query = [
company: requestObj.company,
date: requestObj.date
type: requestObj.type
]
headers.'User-Agent' = 'Mozilla/5.0'

// when response is a success, parse the gpath xml
response.success = { resp, xml ->
assert resp.statusLine.statusCode == 200
log.info "My response handler got response: ${resp.statusLine}"
// store the list
parsedObjectsFromXml = convertXmlToDomainObjects(xml)
}

// called only for a 404 (not found) status code:
response.'404' = { resp ->
log.info 'HTTP status code: 404 Not found'
}
}
parsedObjectsFromXml
}

def convertXmlToCompanyDomainObject = { GPathResult xml ->
def list = []
// .. implementation to parse the xml and turn into objec
}

def convertXmlToCorporationObject = { GPathResult xml ->
def list = []
// .. implementation to parse the xml and turn into objec
}

Now we can call the issueHttpBuilderRequest method as follows:

def companyObjects = issueHttpBuilderRequest (request, 'companyInformation',

convertXmlToCompanyDomainObject)

def corporationObjects = issueHttpBuilderRequest (request, 'corporationInformation',

convertXmlToCorporationObject)

Summary

The template method pattern is an effective and common pattern solution. However, try using the closure approach and compare the different implementations. It's always good to have another tool in the arsenal.

On the other hand, you could achieve the same by copying and pasting code, or inserting only if statements to handle variances. Punt those options and go for the elegant approach. It's better for maintenance, easier on the eye for other developers, and makes our job easier in the long run.


Further resources on this subject:


 

Books to Consider

comments powered by Disqus