Use interfaces and dynamic class loading for added functionality Have you ever developed a program you knew would require new functionality throughout its lifetime? Requirements state that the marketing department will make all sorts of pricing deals for every customer. Your program needs to handle those new requirements as they come along, or you must enable users to customize your software without the need to change source code.Can you avoid changing the code you have already written and tested to add new functionality? Can you add new classes to your program without having to recompile the whole thing? The answer to both questions is yes, and as you might have guessed, is based on interfaces and dynamic class loading.As an aside, most of the classes and architecture used in this article are simplified from their use in professional programming. The overall code demonstrates how to use interfaces to dynamically extend programs; it is not intended for use in real systems. What are interfaces?An interface simply describes the way an object is called. When you define an interface, you relate how other objects will use a specific object.Most of you working with Java should already understand interfaces, as it is difficult to use Java without that knowledge. But for those of you unclear on the subject, I’ll start at the beginning and then create more complicated examples. If you already understand interfaces, you can probably skim until we get to the Using Strings to Specify Class Name section.The power of interfacesThe first example illustrates the power of interfaces. Assume your client is a brokerage house, and they want you to set up their trading system. They trade in all sorts of financial instruments: stocks, bonds, commodities, and so on. Different customers are charged different amounts for their trades; the amounts are defined by what the client calls pricing plans. First, you think about the design of your classes. The main classes and their properties, defined by the client, may be as follows:Customer: Name, Address, Phone, PricingPlanTrade: TradeType (stock, bond, commodity), ItemTraded (stock symbol), NumberOfItemsTraded, ItemPrice, CommissionAmountPricingPlan: Provides a call procedure to calculate the CommissionAmount for a tradeCoding without interfacesYou can code the pricing plans without using an interface and then enhance the code from there. Right now, the client has two pricing plans defined as follows:Plan 1: 0/trade for regular customersPlan 2: 5/trade for the first 10 trades in a month; 0/trade after thatThe Trade object uses a PricingPlan object to calculate how much commission to charge the customer. You create a PricingPlan class for each pricing plan. The class for Plan 1 is called PricingPlan20 and Plan 2’s class is called PricingPlan1510. Both classes calculate the commission to charge using a procedure called CalcCommission(). The code looks like the following: Class Name: PricingPlan20public double calculateCommission( Trade trade ) { return 20.0; } Class Name: PricingPlan1510public 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 interfacesHow can an interface make your life easier in the previous example? You can create the PricingPlan interface that the PricingPlan classes implement:Interface Name: IPricingPlanpublic 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 nameSay 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: Tradepublic 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: Tradepublic 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 namesImagine 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: File Name: PricingPlans.txt1,string_interfaces.PricingPlan20 2,string_interfaces.PricingPlan1510 3,string_interfaces.PricingPlan8 4,string_interfaces.PricingPlan10 Now you can create a PricingPlanFactory class that will return an IPricingPlan instance that corresponds to the PlanId that is passed to it. The class reads and parses the text file into a Map so it can do easy lookups based on the PlanId. But note that you can change the PricingPlanFactory class to use a database or XML file without changing anything outside the class.You can refactor the Customer class so it returns the IPricingPlan instance instead of a PlanId. This design is better than the Customer class returning a PlanId, because other classes won’t have to know that they must pass the PlanId to the PricingPlanFactory() method. Those classes won’t have to know anything about the PricingPlanFactory; they can simply use the IPricingPlan instance as needed. (I used poor design earlier because I felt it more easily illustrated my point.) These changes are located in the pricing_plan_factory package of this article’s source code. Check Resources for download.Take noteThe DynamicJavaSource.zip source code for this article contains a Test class in each package. The following table describes what you’ll find in those packages:Table 1. Packages contained in source codePackageDescription no_interfacesExample with no interfaces. hard_coded_interfacesExample that uses interfaces, but the class names are hardcoded into the source. string_interfacesExample that uses interfaces and has class names as strings in the source. pricing_plan_factoryExample that uses a text file to get the class names. One final note about possible class loader problems: Class loaders work unexpectedly sometimes. For example, if the class that calls the forName() method is an extension, the CLASSPATH directories will not be searched for the class that is being dynamically loaded. If you want an in-depth discussion of this possible problem or are getting unexpected ClassNotFoundExceptions, see information on Class.forName() in Resources. Also, check out the sidebar at the end of this article for tips on versioning your interfaces to avoid eliminating dynamic extensions when your program changes: Versioning Your Extension Interfaces.Flex your appsYou should now have enough knowledge to use interfaces and dynamic class loading to make flexible programs. By example, I showed you how to use a text file to load new functionality to code that uses no interfaces. Experiment with the code, and see how you can extend it. Now you can create programs to which users (other programmers, other departments, or even outside customers) can make additions without needing your source code.Bill W. Davis is a senior consultant with Levi, Ray & Shoup, Inc., a large software and consulting firm headquartered in Springfield, Ill. He is an IBM-certified solution developer (VisualAge for Java), Sun Java 2-certified programmer, and Microsoft-certified solution developer. He is currently working in the manufacturing sector as the lead designer/developer on an IBM WebSphere-based forecasting application. Java