




















































Read more about this book |
(For more resources on Groovy, see here.)
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.
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:
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?
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.