0

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?

5

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();
  • Cool thanks, I actually had something like this in mind but I planned to omit the factory classes, put class references in the map (instead of the factories), and then create the function by getting and invoking the constructor via reflection. But that would probably not by very effective I think... – Jardo Jan 20 '15 at 17:49
  • 1
    In general, avoid reflection if you can. It adds complexity that can be avoided. Factories are complicated enough without it. – Avner Shahar-Kashtan Jan 20 '15 at 17:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.