Tuesday, January 6, 2009

How to Change a Class without Recoding It

When I was an undergrad studying Java programming, I found the explanation of interfaces to be rather unsatisfying. The instructor’s explanation would often begin like this:

Suppose we have a Java interface defined as

package abstraction;

public interface Formula {

public float getResultOfFormula(float x);

}

Therefore, any class that implements the interface called Formula must provide the method body for getResultOfFormula. It must take a floating point number as a parameter and return a floating point number. An implementing class might take the form:

package abstraction;

public class ImplementedFormula implements Formula {

public float getResultOfFormula(float x) {
return (float)Math.exp(x);
}
}

In this implementation, the method returns the exponential of x. Well, so what? It seems that we have accomplished so very little.

To understand how this idea might be very powerful, let’s consider a concrete example. Let’s suppose that we want to create a class called Computational. It has a method called getValue(float x). By default, the method returns 2*x. However, we also want the user to be able to define the formula without having to rewrite the Computational class. Here is how it is done:

package abstraction;

public class Computational {

private abstraction.Formula userDefinedFormula = null;

public void defineFormula(abstraction.Formula f){
userDefinedFormula = f;
}

public float getValue(float y){
if(userDefinedFormula == null){
return 2*y;
}else{
return userDefinedFormula.getResultOfFormula(y);
}
}

}

In the first line, we provide for a variable of the type Formula and initially set it to null. While it is true that interfaces cannot be instantiated, classes that implement interfaces can certainly be instantiated. So, an instantiation of the class ImplementedFormula is of the type Formula.

Indeed, the method defineFormula(Formula f) allows the user to pass a class that has implemented the Formula interface, and the userDefinedFormula variable is set to the parameter.

The method getValue(float y) has an if-statement to check the nullity of the variable userDefinedFormula. If it is null, then the user has not defined the formula and the default of 2*y is used. Otherwise, the method returns the user’s definition. The class called Computational is guaranteed that the method getResultsOfFormula(float y) exists because it must have implemented the Formula interface.

To see how the Computational class might be used in practice, consider this code:

package abstraction;

public class WorkingClass {

public void runThisClass(){
Computational comp = new Computational();
comp.defineFormula(new ImplementedFormula());
System.out.println(comp.getValue(30.0f));
}

public static void main(String[] args) {
WorkingClass wc = new WorkingClass();
wc.runThisClass();

}

}


If this class is run, the output will be 1.06864742E13, which is the exponential of 30. However, if the second line in the runThisClass() method is commented out so that the formula is not user-defined, the output is 60, 2*30. The Computational class has used the default formula of 2*x.

The Computational class could be enhanced by adding another method called removeFormula() such as:

public void removeFormula(){
userDefinedFormula = null;
}


By resetting the variable userDefinedFormula back to null, the method getValue() will use the default.

Finally, the user is not restricted to a single implementation of the Formula interface. Indeed, the user might implement it several times, with the user re-defining the getValue(float x) as the user wishes.

No comments: