2015/10/12

Static vs dynamic method overloading with Java and Groovy

Groovy code may be quite similar to Java at the first glimpse. This may sometimes lead to caveats. A good example of such is method overloading by an argument type. Code may look the same in both languages but it's going to work different.

Example

Consider the following class:

public class Foo {

   public String bar(Object value) {
      return "Object: " + value;
   }

   public String bar(String value) {
      return "String: " + value;
   }

   public String bar(Integer value) {
      return "Integer: " + value;
   }
}

Although it's a Java class it doesn't really matter at this point whether it's Groovy or Java. The caller class matters.

JAVA: static binding for overloaded methods

Caller class written in Java:
public class StaticBindingExample {

   public static void main(String[] args) {
      Object number = 44;
      Object text = "plop!";

      Foo foo = new Foo();

      foo.bar(number);   // returns "Object: 44"
      foo.bar(text);     // returns "Object: plop!"
   }

}
By Java static nature, calling the bar() method always invokes the one which signature matches the argument declared type.

Groovy: dynamic binding for method overloading

That's the exact same code for caller class as in the previous snippet just written in Groovy:
class DynamicBindingExample {

   static void main(String[] args) {
      Object number = 44
      Object text = 'plop!'

      Foo foo = new Foo()

      foo.bar(number)  // returns "Integer: 44"
      foo.bar(text)    // returns "String: plop!"
   }
}
Here we can see the difference. Despite the arguments for the method call were declared as Object, Groovy dynamic type evaluation always tries to match the closest matching method at runtime. Thus methods relevant for the actual argument type were executed.

Advantage of dynamic binding

Please consider an example of some payment service written in Java:
public class PaymentService {

   private final CustomerRepository customerRepository;
   private final AccountService accountService;

   public PaymentService(CustomerRepository customerRepository, AccountService accountService) {
      this.customerRepository = customerRepository;
      this.accountService = accountService;
   }

   public void pay(Integer customerId, BigDecimal amount) {
      Customer customer = customerRepository.findById(customerId);   // may throw UnknownCustomerException
      accountService.substract(customer, amount);                    // may throw InsufficientFundsException
   }
}
How is going to look exception handling if we add try-catch block around business logic within the pay() method? Well, quite typical:
public void pay(Integer customerId, BigDecimal amount) {
   try {
      Customer customer = customerRepository.findById(customerId);   // may throw UnknownCustomerException
      accountService.substract(customer, amount);                    // may throw InsufficientFundsException
   } catch (UnknownCustomerException ex) {
      handle(ex);
   } catch (InsufficientFundsException ex) {
      handle(ex);
   } catch (Exception ex) {
      handle(ex);
   }
}

private void handle(UnknownCustomerException ex) {
   // relevant logic for handling unknown customer
}

private void handle(InsufficientFundsException ex) {
   // relevant logic for handling insufficient funds
}

private void handle(Exception ex) {
   // relevant logic for handling unexpected exception
}
Obviously if the handling logic isn't complex, delegation to separate methods may be skipped in favour of in-line handling inside of each catch block. Although splitting the logic into methods or encapsulating exception handling in injected collaborator is usually a better way and cleaner separation of concerns.

This way or the other we may see straight away that catch blocks are rather redundant in this situation. How would it look like with Groovy's dynamic overloading? It’s enough to have single, generic type, catch block. Invocation is routed to appropriate handle() method by the argument type anyway:
void pay(Integer customerId, BigDecimal amount) {
   try {
      Customer customer = customerRepository.findById(customerId)   // may throw UnknownCustomerException
      accountService.substract(customer, amount)                    // may throw InsufficientFundsException
   } catch (Exception ex) {
      handle(ex)
   }
}

private void handle(UnknownCustomerException ex) {
   // relevant logic for handling unknown customer
}

private void handle(InsufficientFundsException ex) {
   // relevant logic for handling insufficient funds
}

private void handle(Exception ex) {
   // relevant logic for handling unexpected exception
}
The same implemented with the less amount of a cleaner code? That's what a craftsman appreciates.

The same behaviour with Java static overloading

There is a way to achieve "the same" with pure Java, the Match Maker Design Pattern. I'm not sure about the name of the pattern itself though. I've got the feeling that Martin Fowler, Gang of Four or some other guru might have come with a better definition for such a case. I can't find it at the moment so let’s get back to the code (you're welcome to comment if you know it though).

Simply we may have a "routing" map of class type to be handled to the handler for it. In our case it can be done by adding mentioned map as the PaymentService class field and then using it in the catch block as follows. Let say the handlers map is injected via constructor then we simple have a few more lines of code:
public class PaymentService {
   // …
   private final Map<Class<? extends Exception>, ExceptionHandler> handlers;

   public PaymentService(CustomerRepository customerRepository, AccountService accountService,
                         Map<Class<? extends Exception>, ExceptionHandler> handlers) {
      // … 
      this.handlers = handlers;
   }

   public void pay(Integer customerId, BigDecimal amount) {
      try {
         // …
      } catch (Exception ex) {
         ExceptionHandler handler = handlers.get(ex.getClass());
         handler.handle(ex);
      }
   }
}
Obviously we need the handler interface as well, nothing surprising here:
interface ExceptionHandler {
   public void handle(Exception ex)
}
That's it, isn't it? Well, not quite, to be honest. To get the proper impression of how much more code actually is necessary the best way is to show the complete example. If we were about to encapsulate the same, full logic within a single class it would look like:
public class PaymentService {

   private final CustomerRepository customerRepository;
   private final AccountService accountService;
   private final Map<Class<? extends Exception>, ExceptionHandler> handlers;

   public PaymentService(CustomerRepository customerRepository, AccountService accountService,
                         Map<Class<? extends Exception>, ExceptionHandler> handlers) {
      this.customerRepository = customerRepository;
      this.accountService = accountService;
      this.handlers = createExceptionHandlers();
   }

   public void pay(Integer customerId, BigDecimal amount) {
      try {
         Customer customer = customerRepository.findById(customerId);   // may throw UnknownCustomerException
         accountService.substract(customer, amount);                    // may throw InsufficientFundsException
      } catch (Exception ex) {
         ExceptionHandler handler = handlers.get(ex.getClass());
         handler.handle(ex);
      }
   }

   private Map<Class<? extends Exception>, ExceptionHandler> createExceptionHandlers() {
      HashMap<Class<? extends Exception>, ExceptionHandler> handlers = new HashMap<>();
      handlers.put(UnknownCustomerException.class, createUnknownCustomerExceptionHandler());
      handlers.put(InsufficientFundsException.class, createInsufficientFundsExceptionHandler());
      handlers.put(Exception.class, createUnexpectedExceptionHandler());
      return handlers;
   }

   private ExceptionHandler createUnknownCustomerExceptionHandler() {
      return new ExceptionHandler() {
         @Override
         void handle(Exception ex) {
            // relevant logic for handling unknown customer
         }
      };
   }

   private ExceptionHandler createInsufficientFundsExceptionHandler() {
      return new ExceptionHandler() {
         @Override
         void handle(Exception ex) {
            // relevant logic for handling insufficient funds
         }
      };
   }

   private ExceptionHandler createUnexpectedExceptionHandler() {
      return new ExceptionHandler() {
         @Override
         void handle(Exception ex) {
            // relevant logic for handling unexpected exception
         }
      };
   }

}

Summary

Clearly solution complexity may grow really fast if one wants to mimic dynamic binding behaviour in a language which by its nature does it the static way. Dynamic method overloading comes then as really helpful thing which allows avoiding unnecessary clutter in the code.

On the other hand it suits well rather simpler scenarios of dealing with objects from the same inheritance tree. It doesn't have to always be the best approach though. Too much logic placed within a single class is almost never a good idea. As usual the trick is to choose the proper solution for the job as well as the programming language itself.

18 comments:

  1. the blog is very nice and interesting. thank you for sharing the blog with us. keep on updating.
    Software Testing Training in Chennai

    ReplyDelete
  2. its really useful information of sharing about java using static and dynamic process. keep sharing more article like this.
    Software Testing Training in Chennai

    ReplyDelete
  3. Excellent post and wonderful blog full of fantastic applications Live Updates

    ReplyDelete
  4. It’s really amazing that we can record what our visitors do on our site. Thanks for sharing this awesome guide. I’m happy that I came across with your site this article is on point,thanks again and have a great day.

    SSAS Training in Chennai

    ReplyDelete
  5. These provided information was really so nice,thanks for giving that post and the more skills to develop after refer that post. Your articles really impressed for me,because of all information so nice.

    SAP training in Chennai

    ReplyDelete
  6. These provided information was really so nice,thanks for giving that post and the more skills to develop after refer that post. Your articles really impressed for me,because of all information so nice.

    SAP training in Chennai

    ReplyDelete
  7. Great Article I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because it becomes more and more interesting from the starting lines until the end. So Thank you for sharing a COOL Meaningful stuff with us Keep it up..!

    SAP training in Chennai

    ReplyDelete
  8. This is the best application on the drop today. There access to enjoy great moments of relaxation: age of war 2|
    age of war 5
    Great! Thanks for sharing the information.Summon creatures to fight enemy units and demolish the opposing castle. Your castle is equipped with a crossbow, which you can use to shoot enemies age of war 6. Make sure you upgrade skills to increase your chances of winning battles.
    The goal of Age of War is to survive longer than the computer and to outlast him you’ll need to train the right troops while balancing your offence and defence in this high paced, quick thinking flash game age of war 4
    . Train troops of you own to combat the computers. As you kill off the computer troops, you will gain EXP and you will eventually advance to the next age.
    Choose a starter deck and prepare for an epic war!age of war 3

    Command your units in each battle to attack the enemy’s castle, while protecting your own base earn to die 2. Earn and upgrade cards to help you conquer the land.age of war

    happy wheels | tank trouble
    Thanks for the best blog.it was very useful for me.keep sharing such ideas in the future as well.this was actually what i was looking for,and i am glad to came here!
    cubefield It contains a plethora of tools and objects for level building such as harpoon guns,blocks and vans. Users can upload their maps to a public server where they are accessible

    ReplyDelete
  9. Really, these quotes are the holistic approach towards mindfulness. In fact, all of your posts are. Proudly saying I’m getting fruitfulness out of it what you write and share. Thank you so much to both of you.

    Online Training in Chennai

    ReplyDelete
  10. This blog having the details of Processes running. The way of runing is explained clearly. The content quality is really great. The full document is entirely amazing. Thank you very much for this blog.
    PPC Services Chennai

    ReplyDelete
  11. This comment has been removed by a blog administrator.

    ReplyDelete
  12. very useful information provided in this blog. concepts were explained in a detailed manner. Keep giving these types of information.
    SEO company in Chennai

    ReplyDelete
  13. I have read your blog its very attractive and impressive. I like it your blog.

    Java Training in Chennai Java Training in Chennai | Core Java Training in Chennai

    ReplyDelete