Newsletter sign-up
View all newsletters

Sign up for our technology specific newsletters.

Enterprise Java
Email Address:

Dynamically extend Java applications

Use interfaces and dynamic class loading for added functionality

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone

Page 2 of 3

Class Name: PricingPlan20

public double calculateCommission( Trade trade )
{
   return 20.0;
}


Class Name: PricingPlan1510

public double calculateCommission( Trade trade )
{
   double commission = 0.0;
   if( trade.getCustomer().getNumberOfTradesThisMonth() <= 10 )
      commission = 15.0;
   else
      commission = 10.0;
   return commission;
}


Here's the code to get the commission in Trade:

public double getCommissionPrice()
{
   double commissionPrice = 0.0;
   if( getCustomer().getPlanId() == 1 )
   {
      PricingPlan20 plan1 = new PricingPlan20();
      commissionPrice = plan1.calculateCommission( this.getCustomer() );
      plan1 = null;
   }
   else
   {
      PricingPlan1510 plan2 = new PricingPlan1510();
      commissionPrice = plan2.calculateCommission( this.getCustomer() );
      plan2 = null;
   }
   return commissionPrice;
}


Hardcoded interfaces

How can an interface make your life easier in the previous example? You can create the PricingPlan interface that the PricingPlan classes implement:

Interface Name: IPricingPlan

public interface IPricingPlan {
    public double calculateCommission( Trade trade );
}


Since you are defining an interface, you don't define a body for the calculateCommission() method. The actual PricingPlan classes will fill in the code. The first modification you make to the PricingPlan classes is to declare that you will implement the interface you just defined. You do this by adding the following code to the top of the PricingPlan class definitions:

public class PricingPlan20 extends Object implements IPricingPlan {


When you declare that you will implement an interface in Java, you must provide bodies for all the methods that the interface defines (unless you are creating abstract classes, which is unrelated to this topic). So any class that implements the IPricingPlan interface must define a method called calculateCommission(). The method signature must be defined exactly the way the interface defines it, so it must accept a Trade object. Since we already had calculateCommission() methods in both PricingPlan classes, we won't modify them any further. If you create new PricingPlan classes, they must implement the IPricingPlan interface and the calculateCommission() method.

Next you can change the Trade class's getCommissionPrice() method to use the interfaces as follows:

Class Name: Trade

public double getCommissionPrice()
{
   double commissionPrice = 0.0;
   IPricingPlan plan;
   if( getCustomer().getPlanId() == 1 )
   {
      plan = new PricingPlan20();
   }
   else
   {
      plan = new PricingPlan1510();
   }
   commissionPrice = plan.calculateCommission( this );
   return commissionPrice;
}


Notice how you define the PricingPlan variable as an IPricingPlan interface. The instance you actually create depends on the customer's pricing plan. Since both PricingPlan classes implement the IPricingPlan interface, you can set the variable equal to a new instance of either one. Java, in general, doesn't care about the actual object that implements the interface, only about the interface itself.

Using strings to specify class name

Say your boss comes to you and says that the company just approved two new pricing plans, with more coming. The pricing plans are a flat rate of and 0 per trade. You decide to create two new PricingPlan classes: PricingPlan8 and PricingPlan10.

In that case, you must change the Trade class to include these new PricingPlans. You could simply add more if/then/else clauses, but that would get unwieldy as the number of pricing plans grows. Another option is to use the Class.forName() method instead of new when creating PricingPlan instances. Class.forName() lets you create instances using a string to name the class instead of hardcoding the name in the program. Here is an example of how you could use this in the Trade class's earlier code:

Class Name: Trade

public double getCommissionPrice()
{
   double commissionPrice = 0.0;
   IPricingPlan plan;
   Class commissionClass;
   try
   {
      if( getCustomer().getPlanId() == 1 )
      {
         commissionClass = Class.forName( "string_interfaces.PricingPlan20" );
      }
      else
      {
         commissionClass = Class.forName( "string_interfaces.PricingPlan1510" );
      }
      plan = (IPricingPlan) commissionClass.newInstance();
      commissionPrice = plan.calculateCommission( this );
   }
   // ClassNotFoundException, InstantiationException, IllegalAccessException
   catch( Exception e )
   {
      System.out.println( "Exception occurred: " + e.getMessage() );
      e.printStackTrace();
   }
   return commissionPrice;
}


This code doesn't offer much of an advantage over the previous code. It's actually longer considering you must now include the exception-catching code. Look at what happens if you create an array of PricingPlan class names in the Trade class:

Class Name: Trade

public class Tradeextends Object {
   private Customer customer;
   private static final String[]
      pricingPlans = {  "string_interfaces.PricingPlan20",
                        "string_interfaces.PricingPlan1510",
                        "string_interfaces.PricingPlan8",
                        "string_interfaces.PricingPlan10"
      };


Now you can change the getCommissionPrice() method to the following:

Class Name: Trade

public double getCommissionPrice()
{
   double commissionPrice = 0.0;
   IPricingPlan plan;
   Class commissionClass;
   try
   {
      commissionClass =
      Class.forName( pricingPlans[ getCustomer().getPlanId() - 1 ]  );
      plan = (IPricingPlan) commissionClass.newInstance();
      commissionPrice = plan.calculateCommission( this );
   }
   // ClassNotFoundException, InstantiationException, IllegalAccessException
   catch( Exception e )
   {
      System.out.println( "Exception occurred: " + e.getMessage() );
      e.printStackTrace();
   }
   return commissionPrice;
}


If you don't count the exception handling, this code is by far the simplest we've seen. It is also relatively easy to add new pricing plans. You simply create and add the plans to the Trade class's array.

I hope you begin to see the power available with dynamic class loading. Now I'll switch gears and refactor my design for cleaner code. (See the Resources section for more information on refactoring.)

You can improve this design to make adding new pricing plans even easier. You still have to recompile the source file that contains the Trade class whenever a new pricing plan is added.

Database/XML-based class names

Imagine what would happen if you stored the class names in a database table, XML file, or even a plain text file. You could then add pricing plans by simply creating a new class, placing it where the program can find it, and adding a record to the database table or file. Then you wouldn't need to change the Trade class every time a new pricing plan was introduced. I will use a plain text file, since that is the easiest to code and makes for a simpler example. In a real system, I would certainly recommend either a database or XML file, since they allow more flexibility. The text file looks like the following:

  • Digg
  • Reddit
  • SlashDot
  • Stumble
  • del.icio.us
  • Technorati
  • dzone
Comment
Login
Forgot your account info?
Add comment
Anonymous comments subject to approval. Register here for member benefits.
Have a JavaWorld account? Log in here. Register now for a free account.
Resources