The code I'm writing (MyService
) includes the ability for each customer to plug-in their own calculator at a particular point in the processing. This is to allow customization of business rules. At the point the calculation is done we know a variety of things, some of which may be relevant to the calculation. MyService
will be created for a particular set of input parameters and run once.
My plan is to use dependency injection to give MyService
a calculator
in the constructor. This allows different users to plug-in their own calculator
. The calculator
will return an amount representing the surcharge payable for this particular run of MyService
. Others will be implementing a variety of calculators and I'll need to be able to update my code without breaking theirs. ie. keep backwards compatability.
The problem I have is that the various calculator implementations will require different parameters. These parameters can not be constructor injected into the calculator at the time MyService
is created because they are not known some processing in MyService
occurs.
The calculator will only be called once within a particular instantiation of MyService
. Therefore at one extreme all parameters could be passed in the constructor and there is a method with no parameters that returns the answer. At the other, all parameters are passed in the method invocation.
AlwaysZeroCalculator
might just return 0
so no parameters are required. PercentageCalculator
needs amount
in order to apply a percentage. A yet more complicated one needs the amount
and customerNumber
. We can assume that whatever the calculator
might need is known to MyService
at runtime (or itself can be injected into the calculator
implementation, like a hibernate Session).
How can I implement this?
Here are some options and problems:
- Make all calculators implement an interface that includes all parameters as method arguments. But, if something extra is added then they all need to change which will inevitably turn this into the second option.
- Make different interfaces (
ICalculator
,ICalculatorWithAmount
,ICalculatorWithAmountAndCustomerNumber
, etc).MyService
will need to see which interface thecalculator
it has implements, cast it to that interface, and then call the appropriatecalculate(..)
method. - Introduce a parameter object that includes anything any of them care about. This means even the simplest
calculator
depends on everything. - Make different interfaces and different versions of
MyService
that expect one of those interfaces. - Inject a
calculatorFactory
instead of acalculator
. The factory would take all possible parameters and create a calculator with only the right ones. This seem to just move the problem to somewhere else without solving it. - Pass some horrible hashmap to the calculator and type safety, declaration of dependencies be damned
Is there a better way?
Assuming you're using spring ... Perhaps the factory-bean and factory-method attributes of the bean tag may be useful.
Pseudo code ...
These are two problems:
The first one is easy - all calculators do the same thing, can have the same interface. The latter is the one to solve, since it requires the different parameter for each calculator instance.
From what you listed is the factory the best solution. And it is not moving the problem elsewhere, it is solving the problem. Each factory knows its calculator and knows what parameters it needs. So the factory depends on calculator. Creating a calculator using a factory only creates a dependency on creating a calculator and not its parameters or the calculator itself, it can be part of the factory interface that all the factories implement.
Providing parameters for the factory can be solved by property injection using whatever means you choose - Spring definitely not bad in that.
The application in turn only know how to use a calculator so depends on the general calculator interface and not any particular calculator implementation.
This calculator uses an
abstract
FunctionAdapter
to evaluate functions by name. Each concrete subclass implements aneval()
method that takes a variable number of arguments.I always struggle to get runtime dependencies to work with Dependency Injection in an elegant way.
However, I would go for a variation on your "Make different interfaces" plan.
Each calculator would define it's own interface, say
IAlwaysZeroCalculatorParameters
,IPercentageCalculatorParameters
etc.Your
MyService
class can then implement all these interfaces, and then pass itself in to each calculator.There is very little framework overhead to achieve this, and it means that each calculator expresses concisely what it needs. It works very well for me in similar situations, although in your particular case (where you don't have control over the plugins), it means you must release
MyService
every time someone implements a new calculator (so that it can implement the new interface). If required I would probably get round this with some Duck Typing.