Working along with Groovy Closures

Working along with Groovy Closures

Closures, they play a very key role in Groovy vocabulary. They are used everywhere in groovy API. Right from groovy looping structures to Builders to DSL, they integrate quite well with other structures and allows programmers to define code in a more expressive way.

Closure definition:
Closure welcomeMsg = { user ->
println "Welcome $user"
}
Quite Self descriptive, the above will create a closure object which will override closure doCall().

Closure invocation:
a) welcomeMsg("Shroff");
b) welcomeMsg.call("Shroff"); and welcomeMsg.doCall("Shroff");

Closure scope:
Closure accesses variable using 'owner', 'this' & 'delegate' reference
In groovy specifically for closure these are 3 references which will be initialized for each closure
a) 'owner' refers to the block containing the current closure
b) 'this' refers to the current object instance
c) 'delegate' is same as owner reference except that it can be reassigned to some other object.
Closure delegate reference is quite useful for relaying method calls and thus is a fundamental requirement for implementing DSL.


Closures bind over the variables which are accessible at compile time.
For example:
def method(){
def localVar="LocalVar is still alive";
return {
println localVar;
};
}
When closure is constructed in the above call, it will bind in the variables that are in use. A copy of stack variable is made on heap.Thus the variable is still alive when the method returns.

Passing logic around
One of the important aspects of closures is to pass logic around, this allows to centralize code that could have been boilerplated everywhere.
For example Groovy provides various methods related to iteration. These methods (each, times, eachLine, eachFile, etc) accept a closure argument,
centralizing the iteration logic and invoking the client code (closure) for each iteration. Thus allowing programmer to write logic which is central to his application.

Prentended, Relayed Calls
Inside Closures we can make 3 types for method calls
a) Normal method call, b) Pretended method call, c) Relayed method call
Relayed calls are those which are delegated.
Pretended calls are undefined method invocations which are assumed to be present in the Object.

Pretendended & relayed method call is a fundamental to implement Groovy Builders as we will see next.

def sw = new StringWriter();
def markupBuilder = new MarkupBuilder(new PrintWriter(sw));
def antBuild = markupBuilder.project(name:"ant build via groovy", default:"bin"){
description "This is ant file generated from a groovy code"
target(name:"cleanup"){
delete(dir:"dist")
mkdir(dir:"dist")
}

target(name:"bin", depends:"cleanup"){
copy(todir:"dist"){
fileset(dir:"src", includes:"*.word")
}
}
}
println sw

The above will build up an ant build xml using groovy MarkupBuilder. project, description, target are all pretended method calls made on MarkupBuilder.
One of the solution to handle pretended calls is to overriding 'invokeMethod' (which is invoked in case of non-existing method calls). MarkupBuilder relays closure calls to itself by changing closures delegate to itself.

Implementing our own builder
Groovy provides quite a few builders which should be sufficient for most of your needs, the below example is to just thorough our understanding over the concepts we have learned. Below implementation is my flavor of markup builder which is build over pretended and relayed concepts.

Below implementation requires knowledge on Nassi-Shneidermann diagram of Groovy's decision logic for method invocation.

import groovy.xml.*;

class BuilderSupport {
StringBuilder sb = new StringBuilder();
int indent=2;

Object invokeMethod(String methodName, Object args){
sb << "\n"
sb << " " * indent;
sb << "<$methodName"
indent +=2;
def params = args as List
if(params && params.size() >= 1){//attributes and body given
StringBuilder attributeBuilder = new StringBuilder(" ");
if(params[0] instanceof Map)
{
def attributeList = params[0];
attributeList.eachWithIndex { entry, indx ->
attributeBuilder << "${entry?.key} = '${entry?.value}'"
if(indx != attributeList.size()-1)
attributeBuilder << ", ";
}
sb << attributeBuilder
}
sb << ">"
def body = params[params.size()-1];
if(body instanceof Closure){
body.delegate = this;
body();
}
}
indent -=2;
sb << "\n"
sb << " " * indent;
sb << "</$methodName>"
return sb;
}
}

def invoices = new BuilderSupport();
println buildInvoices(invoices);

def buildInvoices(def invoices)
{
invoices.invoices {
invoice(date:new Date()) {
item(count:5){
product(dollar:12, name:'ULC')
}

}
};
}

To summarize, Groovy closure are compact, expressive and powerful groovy feature. Compact/expressive because of its syntax. Powerful because the closure syntax allows to easily define nested structures in a readable manner.

要查看或添加评论,请登录

Sagar Shroff的更多文章

  • Testing LLM Query Outputs with Cosine Similarity

    Testing LLM Query Outputs with Cosine Similarity

    Introduction Few weeks ago, I was pondering on the thought on how to effectively test LLM based application features…

    5 条评论
  • TF-IDF Technique Overview

    TF-IDF Technique Overview

    I am currently learning about feature-engineering, and today I explored the TF-IDF technique. I decided to write it up…

    1 条评论
  • Java - Metaprogramming - Ability to add new functionality to existing API

    Java - Metaprogramming - Ability to add new functionality to existing API

    Ever came across scenario where you wished you could possibly add new functionality to an existing Java API? i.e say…

    2 条评论
  • Approaching test automation development

    Approaching test automation development

    In today's fast-paced product development, test-automation is one of the key to drive the organization's ability to…

    7 条评论
  • Attaching protractor to existing browser instance

    Attaching protractor to existing browser instance

    Protractor-test-runner by default behavior is Creates web-driver instance & opens up browser Executes your tests Kills…

  • How to correctly implement a RetryAnalyzer in TestNG

    How to correctly implement a RetryAnalyzer in TestNG

    A very common requirement while automating testing of a product is you may wish to implement a retry mechanism for a…

    12 条评论

社区洞察

其他会员也浏览了