问题
Consider this real world example, which is code written to make 'GET' and 'POST' calls to the same REST service endpoint via the Rest Client Builder (Grails plugin). I dislike the duplication in that the header and content type setup is identical, but am not sure how to refactor the common pieces out given that they are calling methods on the closure that is being passed to the get() or post() method call. Please provide a concrete example of a good refactoring out of the duplication in your answer.
private def doGetCall(String endpoint, def config) {
def response = new RestBuilder().get(config.baseURI+endpoint) {
contentType("application/json")
header("Authorization", "ApiKey " + config.base64EncodedApiKey)
header("ClientId", config.clientId)
}
handleResponse(response, config, endpoint);
return response;
}
private def doPostCall(String endpoint, def payload, def config) {
def response = new RestBuilder().post(config.baseURI+endpoint) {
contentType("application/json")
header("Authorization", "ApiKey " + config.base64EncodedApiKey)
header("ClientId", config.clientId)
json(payload)
}
handleResponse(response, config, endpoint, payload)
return response;
}
回答1:
Groovy 1.8 added Closure composition, so if you're using a version of Grails which uses Groovy 1.8 or greater:
private def doGetCall(String endpoint, def config) {
def response = new RestBuilder().get(config.baseURI+endpoint, composeRequest(config))
handleResponse(response, config, endpoint);
return response;
}
private def doPostCall(String endpoint, def payload, def config) {
def response = new RestBuilder().post(config.baseURI+endpoint, composeRequest(config, { json(payload) }))
handleResponse(response, config, endpoint, payload)
return response;
}
private def composeRequest(def config, Closure clos = null) {
def request = {
contentType("application/json")
header("Authorization", "ApiKey " + config.base64EncodedApiKey)
header("ClientId", config.clientId)
}
if (clos != null) {
request = request << clos
}
request
}
回答2:
Will this suffice?
class RestTestService {
def rest
def methodMissing(String name, args) {
if( !( name in ['get', 'post'] ) ) { // can add PUT & DELETE in future
// throw missing method exception for method names other than above
throw new MissingMethodException(
"Http Method $name does not exist or not yet implemented.")
}
def (endpoint, config, payload) = args?.toList()
def response = rest."$name"(config.baseURI + endpoint) {
contentType( "application/json" )
header("Authorization", "ApiKey " + config.base64EncodedApiKey )
header( "ClientId", config.clientId )
if ( name == 'post' && payload ) {
json( payload )
}
}
handleResponse(response, config, endpoint)
return response
}
private void handleResponse(def response, def config, def endpoint) { ... }
public def doGetCall(String endpoint, def config) {
get( endpoint, config )
}
public def doPostCall(String endpoint, def payload, def config) {
post( endpoint, config, payload )
}
}
//resources.groovy
beans = {
rest(grails.plugins.rest.client.RestBuilder)
}
Above makes use of methodMissing
in order to decide which http method to call during runtime.
Also note, instead of creating RestBuilder
for each http call, I suggest to use it as a bean as shown above in resources.groovy
and inject it to the class when it is used. If it were a grails artifact (controller, service) then it will be autowired otherwise bean rest
has to be wired appropriately.
You might abstract by using doGetCall
and doPostCall
or totally remove them if required.
来源:https://stackoverflow.com/questions/24792649/how-to-refactor-duplication-out-of-closures-that-are-an-argument-to-a-method-cal