How to provide a plug-in model where different plugins accept different parameters

The code that I write ( MyService ) includes the ability of each client to connect its own calculator at a certain point in processing. This will set up business rules. The moment the calculation is done, we know many things, some of which may be related to the calculation. MyService will be created for a specific set of input parameters and will be launched once.

My plan is to use dependency injection to give MyService a calculator in the constructor. This allows various users to connect their own calculator . calculator returns the amount representing the premium for this particular MyService mileage. Others will implement various calculators, and I will need to update my code without breaking them. i.e. keep compatibility backwards.

The problem is that different calculator implementations will require different parameters. These parameters cannot be entered by the constructor in the calculator at the time of creation of MyService , because they are not known, any processing in MyService occurs.

The calculator will be called only once within a specific instance of MyService . Therefore, on the one hand, all parameters can be passed in the constructor, and there is a method without parameters that returns a response. On the other hand, all parameters are passed in a method call.

AlwaysZeroCalculator can simply return 0 , so no parameters are required. PercentageCalculator requires amount to apply percentage. For even more complex amount and customerNumber required. We can assume that no matter what the calculator may be needed for, it is known at runtime of MyService (or it can itself be introduced into the calculator implementation as a sleep session).

How to implement this?

Here are a few options and problems:

  • Make all calculators implement an interface that includes all parameters as arguments to the method. But, if something superfluous is added, they should all change, which will inevitably turn this into the second option.
  • Make different interfaces ( ICalculator , ICalculatorWithAmount , ICalculatorWithAmountAndCustomerNumber , etc.). MyService will need to see which calculator interface it implements, passes it to this interface, and then calls the corresponding calculate(..) method.
  • Enter a parameter object that includes everything they need. This means that even the simplest calculator depends on everything.
  • Make different interfaces and different versions of MyService that expect one of these interfaces.
  • Add a calculatorFactory instead of a calculator . factory will use all possible parameters and create a calculator with only the correct ones. It seems that this simply transfers the problem to another place without solving it.
  • Spend some awful hash file with calculator and enter security, dependency declaration will be damned

Is there a better way?

+4
source share
4 answers

These are two problems:

  • using a calculator
  • instantiation of calculator

The first one is simple - all calculators do the same, can have the same interface. The latter is the one that needs to be solved, since each instance of the calculator requires a different parameter.

From what you pointed out, factory is the best solution. And this is not a problem elsewhere; it solves the problem. Each factory knows its own calculator and knows what parameters it needs. Thus, the factory depends on the calculator. Creating a calculator using factory creates a dependency on the creation of the calculator, not its parameters or the calculator itself, it can be part of the factory interface, which is implemented by all factories.

Providing parameters for the factory can be enabled by inserting properties using whatever tools you have chosen - Spring is definitely not bad at that.

The application, in turn, knows how to use the calculator, therefore it depends on the general interface of the calculator, and not on the specific implementation of the calculator.

+2
source

Assuming you are using spring ... Perhaps the factory attributes - bean and factory of the bean tag may be useful.

Pseudo code ...

 <bean id="calculator1" factory-bean="calculatorFactory" factory-method="getAlwaysZeroCalculator"> <!--AlwaysZeroCalculator args go here --> </bean> <bean id="calculator2" factory-bean="calculatorFactory" factory-method="getPercentageCalculator "> <!--PercentageCalculator args go here --> </bean> 
+1
source

I always try to make time dependencies work with Injection Dependency in an elegant way.

However, I would go for the option to "Make different interfaces."

Each calculator will define its own interface, say IAlwaysZeroCalculatorParameters , IPercentageCalculatorParameters , etc.

Then your MyService class can implement all of these interfaces, and then pass it on to each calculator.

For this, there is very little overhead for infrastructure, which means that each calculator expresses briefly what it needs. It works very well for me in such situations, although in your specific case (where you do not have control over the plugins), this means that you have to release MyService every time someone implements a new calculator (so that he can implement a new interface ) If necessary, I will probably get around this with Duck Typing.

+1
source

Source: https://habr.com/ru/post/1346860/


All Articles