Life Before the Abacus Formula Compiler
Let’s assume we are doing a typical order processing application. This application needs to compute a rebate for every line item entered. The rebate computation is currently hard-wired as follows:
double computeRebate() { Date orderDate = getOrder().getDate(); double customerRebate = getOrder().getCustomer().getStandardRebate(); double articleRebate = getArticle().getSpecialRebateValidOn( orderDate ); return Math.max( customerRebate, articleRebate ); }
Customers, however, have long been clamoring for a more flexible rebating scheme. Problem is, no two customers want it done the same way. So, in essence, we need to be able to replace the rebate computation with a specific version for nearly each customer!
Management decides that it is too expensive to accommodate all customers, but they do want to make an effort for some key accounts.
Strategy Pattern
We duly refactor the application to make the rebate computation replaceable by introducing a rebate computation strategy:
public static abstract class RebateComputation { static RebateComputationFactory factory = new StandardRebateComputationFactory(); static RebateComputation newInstance( RebateInputs _inputs ) { return factory.newInstance( _inputs ); } protected final RebateInputs inputs; public RebateComputation( RebateInputs _inputs ) { super(); this.inputs = _inputs; } public abstract double getRebate(); } public static abstract class RebateComputationFactory { public abstract RebateComputation newInstance( RebateInputs _inputs ); }
With this, the rebate computation on the line item becomes:
double computeRebate() { RebateInputs inputs = new RebateInputsAdaptor( this ); RebateComputation comp = RebateComputation.newInstance( inputs ); return comp.getRebate(); }
To properly decouple the computations from changes to the line item model, we define an interface, RebateInputs
, through which the computations access the line item:
public static interface RebateInputs { int getCustomerCategory(); int getArticleCategory(); double getCustomerRebate(); double getArticleRebate(); Date getOrderDate(); }
which is implemented by a simple adaptor class accessing the actual line item in question.
Now we write the specific implementations of RebateComputation
for the standard case and our two key accounts and, depending on the program settings, register the appropriate one for use by newInstance
above during startup. Here’s the standard implementation:
static class StandardRebateComputation extends RebateComputation { public StandardRebateComputation( RebateInputs _inputs ) { super( _inputs ); } @Override public double getRebate() { return Math.max( this.inputs.getCustomerRebate(), this.inputs.getArticleRebate() ); } }
External Customization
With the decoupling in place, we realize that we could even open up rebate customization to selected distribution partners. Those, in fact, whom we trust to write Java code to our rebate computation interface specs.
Alas, there are hardly any of those. Still, the few there are now can:
- write their own implementation of the
RebateComputation
class, - compile it, and
- register the resulting .class file with our application.
All we need to do is add a little magic that makes newInstance
load and use the registered .class file.
Not Good Enough!
This, however, still leaves the majority of our customers dissatisfied, with hardly a distributor capable of helping them. And we have opened up a potential security hole in that the system now loads and runs customer-supplied class files.
This kind of problem is where AFC really shines. In the next section, I show you how to make an application customizable using computation defined in spreadsheets – something even savvy users know how to do!