クローズでのオブジェクトとしての関数の使用
アプリケーションのヘルパー・コードを記述する際に、関数をクローズと呼ばれるオブジェクトとして扱うと便利です。
これにより、変数に格納できる関数を定義し、関数パラメータとして受け入れ、引数として別の関数に渡し、後で必要に応じて適切な引数を渡してオンデマンドで呼び出すことができます。
たとえば、オーダーのライン・アイテムの消費税を計算するために異なる戦略をサポートする必要があるアプリケーションを考えてみます。 次に示す
Order_c
オブジェクトのcomputeTaxForOrder()
関数は、Closure
型のtaxStrategyFunction
パラメータを宣言し、コール元から税戦略関数を受け入れます。 コード内の適切な場所で、パラメータ名にカッコを付けて引数を渡すことで、渡された関数を呼び出します。 // 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
あるテリトリABCで、25ユーロ未満の金額は10%の税金を、品目は25ユーロ、超過支払は22%であるとします。 2番目のテリトリDEFでは、売上税は20%フラットです。 これら2つの税金計算戦略を、次に示すように個別の関数変数として表すことができます。 閉じは、明示的な関数名を持たない中カッコで囲まれた関数本文です。 デフォルトでは、クローズ関数本文は、呼び出されたときにパラメータがまったく渡されない場合にnull
に評価されるit
という単一のパラメータを受け入れます。 ここでは、taxForTerritoryABC
という名前の変数に1つの関数本文を保存し、変数taxForTerritoryDEF
に別の関数本文を保存しました。
def taxForTerritoryABC = { return it * (it < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { return it * 0.20 }
関数本文が1行式の場合、次に示すように
return
キーワードを省略できます。Groovyは、return
文を使用して明示的に戻されない場合、最後に評価された式を関数の戻り値として返すためです。def taxForTerritoryABC = { it * (it < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { it * 0.20 }
各無名関数本文内のコードは、後で明示的に呼び出されるまで実行されません。 変数内のコードでは、次に示すように、その変数を引数として
Order_c
オブジェクトのcomputeTaxForOrder()
などのオブジェクト関数に渡すことができます。 ここでは、Order_c
オブジェクトの「挿入前」トリガーからコールします: // 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)
関数に渡される暗黙的パラメータのデフォルト名it
を好まない場合は、次の"arrow" ( ->
)構文を使用して、パラメータに希望する明示的な名前を指定できます。 パラメータ名は左、矢印の右側の関数の本文になります:
def taxForTerritoryABC = { amount -> amount * (amount < 25 ? 0.10 : 0.22) }
def taxForTerritoryDEF = { val -> val * 0.20 }
クロージャは単一のパラメータに限定されません。 computeTaxForOrderInCountry()
という名前のOrder_c
オブジェクトで、次のわずかに異なる税金計算関数を検討してください。 「二番目」引数で呼び出すtaxStrategyFunction
を受け入れます: 課税対象額と国コード。
// 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
に渡すクローズは、次の例に示すように「両方」パラメータを宣言し、それぞれに名前を付ける必要があります。 必要に応じて、関数本文に複数の行を含めることができます。 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 } )
この状況では、構文をさらに簡略化するために、Groovyでは次のような余分なカッコを省略できます:
TotalTax_c = computeTaxForOrderInCountry{ amount, country -> return 0.22 }
多くの組込みコレクション関数 - 詳細は、以降のセクションで説明 - ジョブを成し遂げるための閉鎖を受け入れます。 たとえば、次に示す
findAll()
関数は、.edu
サフィクスで終わるリスト内のすべての電子メール・アドレスを検索します。 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)