I need to use instances which represent mathematical functions along with their parameters end enable their evaluation. I can take 2 approaches:
1: have a single class representing all the math functions:
class TrigonometricFunction extends Function {
private double param;
private String name;
public TrigonometricFunction(double param, String name) {
this.param = param;
this.name = name;
}
public double evaluate() {
double radParam = toRadians(param);
if ("sin".equals(name)) {
return Math.sin(radParam);
}
if ("cos".equals(name)) {
return Math.sin(radParam);
}
// about 10 more functions would go here...
}
}
Advantages of this approach:
- have to create and test just a single class
- no code duplicity (conversion of param to radians)
Disadvantages:
- inefficient evaluation (have to do a lot of String comparisons in evaluate() depending on the function name), but would this even be an issue?
- looks ugly and just doesn’t feel right
2: have a separate class for each math function:
class SinFunction extends Function {
private double param;
public SinFunction(double param) {
this.param = param;
}
public double evaluate() {
double radParam = toRadians(param);
return Math.sin(radParam);
}
}
class CosFunction extends Function {
private double param;
public CosFunction(double param) {
this.param = param;
}
public double evaluate() {
double radParam = toRadians(param);
return Math.cos(radParam);
}
}
Advantages:
- small classes, single responsibility, just seems better
- more effective evaluation as in the first solution (no String comparisons) (?)
Disadvantages:
- have to implement and test a lot of small classes which look basically the same
- duplicate code (calling conversion to radians in each class)
- more expensive to create instances (have to do the String comparison here).
Which approach is better and why? Or do you have a better solution?
I’m assuming that the input that your code receives is the string representation of the function anyway, so there’s no avoiding a string checking. There is, however, ways to avoid some of the other problems you’ve noted.
I would go with your option #2, with the following caveats:
Shared code should be shared
Rather then extending Function
, create an intermediate base class called TrigonometricFunction
that encompasses the shared code – specifically, the conversion to radians:
public abstract class TrigonometricFunction extends Function {
protected double radians;
protected TrigonometricFunction(double paramDegrees) {
radians = toRadians(paramDegrees);
}
abstract double evaluate();
}
And extend that for specific functions.
public class SinFunction extends TrigonometricFunction {
@Override
public double evaluate () {
return Math.sin(radians);
}
}
This way you keep type safety while also reusing code.
Use a dispatcher/factory pattern to avoid if/else string checks
Instead of doing if (name == "sin") { return new SinFunction(params); }
, you can use a Factory object to create the relevant Function object, and choose the relevant Factory using a Hashmap keyed to the function name:
public interface TrigonometricFunctionFactory {
TrigonometricFunction buildFunction(double paramDegrees);
}
public SinFunctionFactory implements TrigonometricFunctionFactory {
@Override
public TrigonometricFunction buildFunction(double paramDegrees) {
return new SinFunction(paramDegrees);
}
}
and so on for other Functions.
Now, create this dispatch table:
HashMap<string, TrigonometricFunctionFactory> dispatchTable = new HashMap<>();
dispatchTable.add("sin", new SinFunctionFactory());
dispatchTable.add("cos", new CosFunctionFactory());
and now you can do this:
String functionName = "sin";
result = dispatchTable[functionName].createFactory(param).evaluate();
2