Groovy Closures Do Not Have Access to Private Methods in a Super Class
Join the DZone community and get the full member experience.
Join For FreeBackground
Groovy closures have the same scope access to class member variables and methods as a regular groovy method. In other words, closures are bound to variables in the scope they are defined. See the codehaus link for the official documentation:
http://groovy.codehaus.org/Closures
This implies that a closure will play by the rules of Object Orientation in the Java language. However, I found that closures do not have access to private methods that are defined in a super class.
The best way to demonstrate is through a short example from Grails. I have used TDD for the example. Note this is a dummy case with no business purpose. Later on I will offer a more reasonable scenario in the business context where I encountered this scenario.
Simple Groovy Example
Take a class that has a closure, a public method, and a private method. Then extend that class. Try and invoke the closure. We get an error.
package closure.access class SendCheckService { def calendarService /** * Closure that invokes a private method */ def closureToSendCheck = { sendPersonalCheck() } def regularMethod() { sendPersonalCheck() } /* * Private method that we want to see executed */ private sendPersonalCheck() { println "Sending Personal Check" } } class FooService extends SendCheckService { }Here are some tests the demonstrate the error.
package closure.access import grails.test.* class SendCheckServiceTests extends GrailsUnitTestCase { def sendCheckService protected void setUp() { super.setUp() sendCheckService = new SendCheckService() } /** * Invocation of the closure from the super class prints out the message: * "Sending Personal Check" */ void testClosureToSendCheck() { sendCheckService.closureToSendCheck() } /** * Invocation of the method from the super class prints out the message: * "Sending Personal Check" */ void testRegularMethod() { sendCheckService.regularMethod() } /** * Invocation of the the closure from the subclass yields an error: * groovy.lang.MissingMethodException: No signature of method: closure.access.FooService.sendPersonalCheck() * is applicable for argument types: () values: [] * This essentially means it does not exists. */ void testFoo_ClosureToSendCheck() { def fooService = new FooService() fooService.closureToSendCheck() } /** * Invocation of the method does not yield and error! */ void testFoo_RegularMethod() { def fooService = new FooService() fooService.regularMethod() } }All is Well
- testClosureToSendCheck() executes fine where the closure can access the private method. This is as expected. It is all well and good because we have not extended class yet.
- testRegularMethod() just demonstrates regular OO principles. A method is able to invoke private methods.
- testFoo_ClosureToSendCheck() invokes a subclass of SendCheckService. It calls the same closure, yet we get served up a MissingMethodException.
- testFoo_RegularMethod() just contrasts testFoo_ClosureToSendCheck(). I invoked this test to show that we should be able to have closures access private methods because other regular methods can!
This may be a double loop learning question any intelligent developer might ask. It questions why we need to even get into this mess. This is a valid point and should be explained.
It is optimal to use closures in an attempt to reuse existing template logic. Let me give a simple business problem.
Imagine we need to code a system that sends out various types of checks: personal checks and business checks. We have the stipulation that these two events must NEVER be done together. Only send a personal check at one instance, and send a business check at another time. However, they both need to follow the same logic. They must be sent on a business day (no holidays or weekends). Thus, we have a scenario where they need the same calendar logic, but it is needed separately.
Duplicate Logic
We could just code the calendar logic twice. (Remember sending business checks and personal checks together in one request cannot occur!)
package closure.access class SendCheckService2 { def calendarService def triggerPersonalCheck () { if (calendarService.todayIsBusinessDay()) { sendPersonalCheck() } else { println "DO NOTHING" } } def triggerBusinessCheck() { if (calendarService.todayIsBusinessDay()) { sendBusinessCheck() } else { println "DO NOTHING" } } private sendPersonalCheck() { println "Sending Personal Check" } private sendBusinessCheck() { println "Sending Business Check" } }Use the DRY Principle
In order to avoid this and follow DRY, we can use closures. Create a method that accepts a closure, and pass it the code snippets to execute in a closure. As a result we have the calendar logic defined once, but executed separately upon a different code snippet.
package closure.access class SendCheckService3 { def calendarService def triggerPersonalCheck() { checkIfBusinessDayAndExecute(sendBusinessCheck) } def triggerBusinessCheck() { checkIfBusinessDayAndExecute(sendBusinessCheck) } def checkIfBusinessDayAndExecute(Closure closure) { if (calendarService.todayIsBusinessDay()) { closure() } else { println "DO NOTHING" } } /** * This is now a closure we can pass around */ def sendPersonalCheck = { println "Sending Personal Check" } /** * This is now a closure we can pass around */ def sendBusinessCheck = { println "Sending Business Check" } }In my real world scenario where I encountered the closure issue, it happened that my closure was trying to execute a private method in an abstract class. This is where I observed the problem.
JVM Thoughts
I honestly do not know the gory details behind why closures in super classes cannot access private methods, but I have an idea. Groovy creates closures by compiling them as inner classes. Since the subclass extends the superclass and then contains a closure, the inner class does not have access to the super class' methods. The reason why a method has access, is because it is compiled as one instance of the class. The closure implementation is not that way (being a inner class), and thus this is why we see a violation of Object Oriented behavior.
If you have a better explanation or futher knowledge of the details behind this issue, please describe them in the comments. Thanks for taking the time to delve in this area.
Thanks to Scott Risk for helping with examples.
Published at DZone with permission of Nirav Assar, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments