Pages

Thursday, June 28, 2012

JAVA - Generics and design

What is generics?

Is a programming functionality which allows a type or method to operate on objects of various types while providing compile-time type safety. [1]  Generics add stability to your code by making more of your bugs detectable at compile time. [2]

The basic syntax in JAVA is <T extends MyClass>, where T is the generic variable to be used in the function implementation which in this case it will handle MyClass and all subclasses.  Imagine you need a function to sort objects of MyClass type and all their subclasses.  Without generics you will need a function per type.  But thanks to generics you can have only one function to handle all subclasses.


Example:  
Non-Generic Generic
public void MyFunction(MyClass aClass);
public void MyFunction2(AnotherClass aClass);
....
public <T extends MyClass> void MyFunction(T aClass);
Duplicate code to handle each subclass Only one function can handle all classes derived from MyClass


For detail implementaion information:

http://docs.oracle.com/javase/tutorial/java/generics/index.html


Generics are useful for...

The power of generics can be appreciated when designing components that other developers will use.  I find this specially important when you need to constrain the domain, but leaving it generic enough.  So why constrain the domain if we are talking about generics usage?  Because in my experience, developers take nasty shortcuts (supposedly to save time...) and use arguments type of strings or objects, or any wtfClass to hack there needs. Having a well define generic function will help other developers understand better the input of a method and avoid errors.


Example:  Generics and Design

In this example application we have an Activity which processes a ResultRes container.  The only purpose of an Activity is to modify the values of the ResultRes object.  You will see how using generics avoids unexpected errors and help developers understand better the classes needed to interact with an activity.

Download:  Source Code
Read the comments in the code for insight about the demonstration...

Parts of the application:

  • ResultRes - is the generic results container.
  • IntegerRes & StringRes - are sub-classes of ResultRes with different value types. 
  • ExecTemplate - is the generic class for activity execution.
    Notice how the template definition is the main issue in the design.
  • ActivityInt & ActivityString - are implementations of the ExecTemplate.
  • ExecGenerics - is just the Main() for testing the project. 

Main Program - usage of generics...

package generics.main;
public class ExecGenerics {

 /**
  * Example application about design and usage of generics.
  * The functionality is just to present how a background processing 
  * can cause exceptions due to bad design implementation which could
  * been avoided.
  */
 public static void main(String[] args) {
  
  boolean isUsingOriginal = true;
  
  // Set starter values...
  // This value will be modified by execute()
  IntegerRes i = new IntegerRes();
  i.setFieldValue(123);   
  StringRes s = new StringRes();
  s.setFieldValue("123");

  // print original values
  System.out.println(i.getFieldValue());
  System.out.println(s.getFieldValue());
 
  if (isUsingOriginal) {
   new generics.original.ActivityInt().execute(i);
   new generics.original.ActivityInt().execute(s); 
   // -- ^^^ this is LEGAL & PROBLEM.. execution will fail!
   new generics.original.ActivityString().execute(s);
  }
  else {
   new generics.improved.ActivityInt().execute(i); 
   // -- ^^^ watch how the helper shows the correct input class 
   // -- ^^^ (Ctrl+Space) inside parenthesis
   // new generics.improved.ActivityInt().execute(s); 
   // -- ^^^  this is NOT LEGAL NOW.. (won't compile)
   new generics.improved.ActivityString().execute(s); 
   // -- ^^^  watch how the helper shows the correct input class 
   // -- ^^^  (Ctrl+Space) inside parenthesis
  }
 
  // print values after processing
  System.out.println(i.getFieldValue());
  System.out.println(s.getFieldValue());  
 }
}


Execution Results

original (isUsingOriginal = true)
123
123
EXEC:  START    - class generics.original.ActivityInt
EXEC:  COMPLETE - class generics.original.ActivityInt
EXEC:  START    - class generics.original.ActivityInt
Exception in thread "main" java.lang.ClassCastException:
 generics.shared.StringRes cannot be cast to generics.shared.IntegerRes
 at generics.original.ActivityInt.performAction(ActivityInt.java:10)
 at generics.original.ExecTemplate.execute(ExecTemplate.java:11)
 at generics.main.ExecGenerics.main(ExecGenerics.java:32)
improved (isUsingOriginal = false)
123
123
EXEC:  START    - class generics.improved.ActivityInt
EXEC:  COMPLETE - class generics.improved.ActivityInt
EXEC:  START    - class generics.improved.ActivityString
EXEC:  COMPLETE - class generics.improved.ActivityString
999
999

Execute Template - Generic template design...

original
package generics.original;
public abstract class ExecTemplate {

 protected abstract void performAction(ResultRes res);
 
 public void execute(ResultRes res) {
   System.out.println("EXEC:  START    - " + this.getClass().toString());
   this.performAction(res);
   System.out.println("EXEC:  COMPLETE - " + this.getClass().toString());  
 }
}
improved
package generics.improved;
public abstract class ExecTemplate<T extends ResultRes> {

 protected abstract void performAction(T res);
 
 public void execute(T res) {
   System.out.println("EXEC:  START    - " + this.getClass().toString());
   this.performAction(res);
   System.out.println("EXEC:  COMPLETE - " + this.getClass().toString());  
 }
}

Activity - No casting needed when well designed...

original
package generics.original;
public class ActivityInt extends ExecTemplate {

 @Override
 protected void performAction(ResultRes res) {
  IntegerRes r = (IntegerRes) res;  // <-- Casting is required and not safe...
  r.setFieldValue(999);
 }
}
improved
package generics.improved;
public class ActivityInt extends ExecTemplate<IntegerRes> {

 @Override
 protected void performAction(IntegerRes res) {
  res.setFieldValue(999);  // <-- No casting required and enforces type
 }
}


No comments:

Post a Comment