Using Functions as Objects with Closures
While writing helper code for your application, you may find it handy to treat a function as an object called a closure.
It lets you to define a function you can store in a variable, accept as a function parameter, pass into another function as an argument, and later invoke on-demand, passing appropriate arguments as needed.
Order_c
object's
computeTaxForOrder()
function shown below declares a
taxStrategyFunction
parameter of type Closure
to
accept a tax strategy function from the caller. At an appropriate place in the code, it
invokes the function passed-in by applying parentheses to the parameter name, passing
along any
arguments.// Object function on Order_c object
// Float computeTaxForOrder(Closure taxStrategyFunction)
Float totalTax = 0
// Iterate over order line items and return tax using
// taxStrategyFunction closure passed in
def orderLines = OrderLinesCollection_c
orderLines.reset()
while (orderLines.hasNext()) {
// Invoke taxStrategyFunction() passing current line's LineTotal_c
def currentLine = orderLines.next()
totalTax += taxStrategyFunction(currentLine.LineTotal_c)
}
return totalTax
In one territory ABC, imagine that amounts under 25 euros pay 10% tax while items
25 euros or over pay 22%. In a second territory DEF, sales tax is a flat 20%. We
could represent these two tax computation strategies as separate function variables as
shown below. The closure is a function body enclosed by curly braces that has no
explicit function name. By default the closure function body accepts a single parameter
named it
that will evaluate to null
if no parameter is
passed at all when invoked. Here we've saved one function body in the variable named
taxForTerritoryABC
and another in the variable
taxForTerritoryDEF
.
def taxForTerritoryABC = { return it * (it < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { return it * 0.20 }
return
keyword as shown below, since Groovy returns the last evaluated expression as the
function return value if not explicitly returned using the return
statement.def taxForTerritoryABC = { it * (it < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { it * 0.20 }
Order_c
object's
computeTaxForOrder()
as shown below. Here we're calling it from a
Before Insert trigger on the Order_c
object:// Before Insert trigger on Order_c
def taxForTerritoryABC = { it * (it < 25 ? 0.10 : 0.22) }
// Assign the value of TotalTax_c field, using the taxForTerritoryABC
// function to compute the tax for each line item of the order.
TotalTax_c = computeTaxForOrder(taxForTerritoryABC)
If you don't like the default name it
for the implicit parameter passed
to the function, you can give the parameter an explicit name you prefer using the
following "arrow" (->
) syntax. The parameter name goes on the left,
and the body of the function on the right of the arrow:
def taxForTerritoryABC = { amount -> amount * (amount < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { val -> val * 0.20 }
The closure is not limited to a single parameter. Consider the following slightly
different tax computation function on the Order_c
object named
computeTaxForOrderInCountry()
. It accepts a
taxStrategyFunction
that it invokes with two arguments: an
amount to be taxed and a country code.
// Object function on Order_c object
// BigDecimal computeTaxForOrderInCountry(Closure taxStrategyFunction) {
BigDecimal totalTax = 0
// Iterate over order line items and return tax using
// taxStrategyFunction closure passed in
def orderLines = OrderLinesCollection_c
orderLines.reset()
while (orderLines.hasNext()) {
// Invoke taxStrategyFunction() passing current line's LineTotal_c
// and the CountryCode_c field value from the owning Order_c object
def currentLine = orderLines.next()
totalTax += taxStrategyFunction(currentLine.LineTotal_c,
currentLine.Order_c.CountryCode_c)
}
return totalTax
computeTaxForOrderInCountry
must
declare both parameters and give each a name as shown in the example below.
Notice that the function body can contain multiple lines if
needed.def taxForTerritoryABC = { amount, countryCode ->
if (countryCode == 'IT') {
return amount * (amount < 25 ? 0.10 : 0.22)
}
else {
return amount * (amount < 50 ? 0.12 : 0.25)
}
}
// Before Insert trigger on Order_c: Assign TotalTax_c
// using a flat 0.22 tax regardless of countryCode
TotalTax_c = computeTaxForOrderInCountry( { amount, country -> return 0.22 } )
TotalTax_c = computeTaxForOrderInCountry{ amount, country -> return 0.22 }
findAll()
function shown below finds all email addresses in the list that end with the
.edu
suffix.def recipients = ['sjc@example.edu','dan@example.com',
'spm@example.edu','jim@example.org']
def eduAddreses = recipients.findAll{ it?.endsWith('.edu') }
def logCurrentTime = { -> println("Current time is ${now()}") }
// Invoke the closure's function body with no arguments
logCurrentTime()
// This will FAIL because the closure demands no arguments!
logCurrentTime(123)