Menu

API Evolution

Version design pattern was designed to solve interoperability problem with old components in new version of the program. For example if you start using new version of Firefox or Eclipse, the plugins written for older version don't work anymore for the new version. Thus many users wait until their favorite plugins are updated and are working with the new version of the application and only then they can begin to use it. Some of plugins will not be further developed and can't be used therefore in newer versions of the application.

The version design pattern allows developing an application by supporting old API, but implementing the new API with new features. Next you can see an example which shows the principles of the version design pattern. Imagine you developed a component, which can solve the power of 2 for any float value.

/**
 * first implementation of the MathSolver, which can be used to solve a square of a value
 */
public class MathSolver_1x0 implements Version{

    /**
     * solves the square of the value
     * @param value
     * @return
     */
    public double solveSquare(double value){
        return value*value;
    }

    public Float getVersion() {
        return 1.0F;
    }

}

Imagine the MathSolver was integrated in a library, which becomes very successful and was used by many users in many products. After few years many new features were developed and the management decided to publish the new version of the successful library. The new API of the library supports many new features, but means big migration overhead. The old code must be rewritten to support new features of the new library.

For example one of the new features is the new implementation of the MathSolver, which supports the solving of power for any value, see the code below

/**
 * second implementation of the MathSolver which can solve the power of any value
 */
public class MathSolver_2x0 implements Version{

    public double solvePower(double value, int power){
        double result = 1;
        for(int i=0; i
            result *= value;
        }
        return result;
    }

    public Float getVersion() {
        return 2.0F;
    }

}

As you can see the MathSolver_1x0.solveSquare(double value) and MathSolver_2x0.solvePower(double value, int power) have different API. However the MathSolver_2x0 can all what MathSolver_1x0 do and even more! This is very important point, which shows that the successor component versions has usually more functionality as predecessors. The main idea of the version design pattern is to re-implement previous version by adopting it to the new version. For example as you can see in the next code example the MathSolver_1x0.solveSquare(double value) was re-implemented and adopted to the new MathSolver_2x0.solvePower(double value, int power).

/**
 * reimplemented version 1x0 of MathSolver. 
 * The implementation contains adaption code to the version 2x0
 */
public class MathSolver_1x0 implements Version{
    MathSolver_2x0 mathSolver2x0 = new MathSolver_2x0();

    public double solveSquare(double value){
        //delegate method to the vers2x0
        return mathSolver2x0.solvePower(value, 2);
    }

    public Float getVersion() {
        return 2.0F;
    }

}

After re-implemnting of MathSolver_1x0 and adapting it to the MathSolver_2x0 any created code will be supported in the new library version. See below a sample program which "was writen years ago" and uses MathSolver_2x0.

/**
 * Program-A was written for library1x0, but it works with all successor version of the library
 */
public class ProgramA {

    public static void main(String[] args) {
        MathSolver_1x0 solver = new MathSolver_1x0();
        double result = solver.solveSquare(4);
        System.out.println("4^2=" + result);
    }

}

The output:

4^2=16.0

This program will work with the new library as well as with the old one! Important is also that all JUnit tests written for the library1x0 will implicitly improve the test coverage for library2x0 and ensure the correctness of the implementation of the adaption code!

Below is presented the Program-B, which uses the 2x0 library version.

/**
 * uses the MathSolver2x0
 */
public class ProgramB {

    public static void main(String[] args) {
        MathSolver_2x0 solver = new MathSolver_2x0();
        double result = solver.solvePower(4,16);
        System.out.println("4^16=" + result);
    }

}

The second library becomes a success again and is now used in thousands of applications. After few years the new version of the library coming out and again it supports all features of old libraries!

For example the MathSolver_3x0 supports the solving of any formulas. It means you can solve the square or the power of the value by creating such formulas and solving them in MathSolver_3x0.solveFormula(String formula, HashMap parameters) see code below:

/**
 * this math-solver version can solve any formulas-expression
 */
public class MathSolver_3x0 implements Version{

    public double solveFormula(String formula, HashMap parameters) throws ParsingException, EvalException{
        //create mathematical expression from the formula
        Expression e = ExpressionFactory.createExpression(formula);
        //create parameters
        Parameters p = ExpressionFactory.createParameters();
        for(Entry entry:parameters.entrySet()){
            MathematicalElement value = NumberFactory.createReal(entry.getValue());
            p.addParameter(entry.getKey(),value);            
        }
        //solve the formula-expression for the parameters
        return e.evaluate(p).getRealValue();
    }

    public Float getVersion() {
        return 3.0F;
    }

}

In the library 3x0 the MathSolver2x0 must be re-implemented and adapted to the version 3x0 see code below:

/**
 * this is adapted 2x0 version which wraps the functionality to the 3x0 version
 */
public class MathSolver_2x0 implements Version{
    /**
     * wrap method to that field
     */
    MathSolver_3x0 mathSolver3x0 = new MathSolver_3x0();

    public double solvePower(double value, int power){
        HashMap parameters = new HashMap();
        parameters.put("x", (float)value);
        String formula = "f(x)=x^2";
        double result = Double.NaN;
        try {
            result = mathSolver3x0.solveFormula(formula,parameters);
        } catch (ParsingException e) {
            e.printStackTrace();
            throw new InternalError("parsing error in expression4J");
        } catch (EvalException e) {
            e.printStackTrace();
            throw new InternalError("evaluation error in expression4J");
        }
        return result;
    }

    public Float getVersion() {
        return 2.0F;
    }

}

:::java
/**
 * uses the MathSolver3x0
 */
public class ProgramC {

    public static void main(String[] args) throws ParsingException, EvalException {
        MathSolver_3x0 solver = new MathSolver_3x0();
        HashMap parameters = new HashMap();
        parameters.put("x", 8F);
        double result = solver.solveFormula("f(x)=x^32", parameters);
        System.out.println("8^32=" + result);
    }
}

The code for version 1x0 must not be changed, because it adapts the code to the version 2x0, and version 2x0 adapts the code to the version 3x0. Thus the program written for version 1x0 will work with the version 3x0. More than that: all JUnit tests will implicitly test the functionality of the version 3x0 and the adaption code of version 1x0 and 2x0!

image

As you can see in the picture above, the library 3x0 is tested by JUnit-tests from versions 1x0 and 2x0, thus the code coverage of the library 3x0 should be better then usual! The JUnit 1x0 tests the adaption code for library 1x0 to library 2x0 and implicitly the adaption code of library 2x0 for library 3x0. The JUnit 2x0 tests test only the adaption code of the librarry 2x0. All earlier written programms (Program-A, -B) are working with the library3x0!