Analytics

Sunday, August 12, 2012

Groovy Closures Do Not Have Access to Private Methods in a Super Class

Recently I came upon a groovy oddity. (At least it is perceived by me to be an oddity). Closures in a groovy class do not have access to a private method if that method is defined in the superclass. This seems odd paired against the fact that regular methods in a super class can access private method defined in the super class

Background

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.
Not as Expected
  • 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!
Why Even Try to Have a Closure Call A Private Method

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(sendPersonalCheck)
  }

  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.

27 comments:

  1. It is a little weird, but you should still make methods that should be visible to subclasses protected, just like in Java.

    p.s. Don't use closures in Grails services - they cannot be transactional since Spring just sees a field, not a method that should be proxied.

    ReplyDelete
  2. method calls within a closure are resolved by:

    Closure itself
    Closure owner
    Closure delegate

    All of those are the subclass class which does not have access to super class private. Should the owner be the superclass?

    ReplyDelete
  3. Thanks Burt. I just simply made it protected to get past the issue.

    Thanks for the tip. I made sure to keep the external interface of Grails services as methods so Spring can handle transactions. My closure is called after that point.

    ReplyDelete
  4. The reason is, Groovy closure is implemented by code generation and a closure is an inner class,

    SendCheckService.class
    FooService.class
    SendCheckService$_closure1.class

    So when you use it from SendCheckService, it will call closure.access.SendCheckService.sendPersonalCheck() and it has access to the private method.

    When you use it from FooService, it will call closure.access.FooService.sendPersonalCheck(), since there is no sendPersonalCheck in FooService, a MissingMethodException is thrown.

    groovy.lang.MissingMethodException: No signature of method: closure.access.FooService.sendPersonalCheck() is applicable for argument types: () values: []

    But if the sendPersonalCheck is protected, then it is visible to FooService, thus it works.

    ReplyDelete
  5. I found some useful information in your blog,thanks for sharing this great topic.The leading online coaching supplier our offers numerous courses on the various technical platform.
    Oracle fusion financials training

    ReplyDelete
  6. 123movies I would like to thank you for the efforts you had made for writing this wonderful piece of writing.

    ReplyDelete
  7. Thanks for sharing this informative content , Great work
    Leanpitch provides online training inScrum Master during this lockdown period everyone can use it wisely.
    Advanced Scrum Master Training

    ReplyDelete
  8. Thanks for sharing this informative content , Great work Mustache Transplant

    ReplyDelete

  9. The common thing to be observed is that each one the spellings have one common vowel that's , 'e'. consistent with this logic, the series should be:
    scrum master interview questions and answers

    ReplyDelete
  10. thanx a lot for taking time out and giving a valuable information to the community appreciated..

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Live Seacoin Price from all markets and SEA coin market Capitalization. Stay up to date with the latest SEA price movements and forum discussion. Check out our snapshot charts and see when there is an opportunity to buy or sell.

    ReplyDelete
  13. I'm glad to read that you have shared such an interesting story. These things are really Informative ideas on Whatsapp Spy Apps and very helpful for other people. It's quality content. You can consider reading more about Celeb Tribe to date to keep posting such good content on your Site

    ReplyDelete
  14. What is the cost of purchasing a domain name? Azure Shield Medical insurance electronic health records programme Michael Eddingsfha pre-approval free employee monitoring software meaws s3 gem facelift fidelity and guaranty life no-fee checking accountanimation the bacon of boca raton plumbers chicken breast that has been wrapped in cream cheese.
    Web Series

    Web Series

    Web Series


    Full HD Web Series

    ReplyDelete
  15. Impressive! I like your post, keep sharing with us! Are you passionate about writing and ready to take your skills to the next level? Look no further than Upskill Rocket's Content Writing Course Training in Bangalore

    ReplyDelete
  16. I will certainly dig it and individually suggest my friends. I’m sure they will be benefited from this web site. The Black Parade Jacket

    ReplyDelete
  17. Thank you so much for your wanderful article .it will inspire the younger youth.
    bhutan coding talent

    ReplyDelete
  18. This article provides a great overview of the benefits and challenges of implementing AI in businesses. I agree that while AI can streamline processes and increase efficiency, it's important to address concerns about job displacement and privacy. It would be interesting to explore case studies of companies that have successfully integrated AI while mitigating these risks.

    ReplyDelete
  19. Disney Watch Party Disney Plus watch party is a browser extension letting you watch Disney Plus alongside friends, wherever they are. Enjoy synchronized video playback, group chat, and video/audio call features. It is perfect for sharing the latest movies/series magic.

    ReplyDelete
  20. Are you looking for the best NGO in Mathura? We are dedicated to helping the community with various programs and support. Join us in making a difference in people's lives. Get involved and be a part of positive change in Mathura.

    ReplyDelete