0

I'm having a problem implementing a strategy pattern for a specific problem i'm encountering.

I basically have a method makes a comparison between values. Apart from the two values, this method relies on two additional parameters to determine which comparison i should do: an operator (equals, not equals,...) and a type (string, double) It is basically a switch which relies on the outcome of another switch.

Let me clarify with an example:

public enum Type {
    STRING,
    BOOLEAN,
    DOUBLE;
}
public enum Operator {
    EQUALS(new Type[] { Type.STRING, Type.BOOLEAN}),
    NOT_EQUALS(new Type[] { Type.STRING, Type.BOOLEAN});

    private Type[] allowedTypes;

    Operator(Type[] allowedTypes) {
        this.allowedTypes = allowedTypes;
    }

    public List<Type> getAllowedTypes() {
        return Arrays.asList(allowedTypes);
    }
}
public class Evaluator {
    public Boolean evaluate(String value1, String value2, Operator operator, Type type) {
        switch(operator) {
            case EQUALS:
                return doEqualComparison(value1, value2, type);
        }

        return null;
    }

    private Boolean doEqualComparison(String value1, String value2, Type type) {
        //code here to check if the params are correct (not null, etc)
        switch(type) {
            case STRING:
                return value1.equals(value2);
            case DOUBLE:
                return Double.parseDouble(value1) == Double.parseDouble(value2);
        }

        return null;
    }
}

And the test

public class EvaluatorTest {
    @Test
    public void testEvaluate() {
        Evaluator evaluator = new Evaluator();

        // check STRING
        Assert.assertTrue(evaluator.evaluate("100", "100", Operator.EQUALS, Type.STRING));
        Assert.assertFalse(evaluator.evaluate("100.00", "100", Operator.EQUALS, Type.STRING));

        // check DOUBLE
        Assert.assertTrue(evaluator.evaluate("100", "100", Operator.EQUALS, Type.DOUBLE));
        Assert.assertTrue(evaluator.evaluate("100.00", "100", Operator.EQUALS, Type.DOUBLE));
    }
}

This works fine but having two (essentially nested) switch statements doesn't seem like a very clean and future proof solution, in case we add alot of Types and Operators things get nasty, pretty quick, so my first refactoring was to change the first switch (Operator) to a Strategy

public class EqualComparisonStrategy implements ComparisonStrategy {
    @Override
    public Boolean compare(String value1, String value2, Type Type) {
        //HERE IS MY PROBLEM
        switch (conditionType) {
            case STRING:
                return value1.equals(conditionValue);
            case DOUBLE:
                return Double.parseDouble(value1) == Double.parseDouble(value2);
        }

        return null;
    }

    @Override
    public boolean accept(Operator operator) {
        return operator == Operator.EQUAL;
    }
}

Although that solution is not "too" bad i guess i would like to externalize the second switch (where my comment is) too, problem is that the implementation of it relies on the the first (operator). I thought about something like StringEqualComparisonStrategy made by either a factory or just do some more stuff in the accept method

//snippet of the accept methode (instead of a factory)
@Override
public boolean accept(Operator operator, Type type) {
    return operator == Operator.EQUAL && type == Type.STRING;
}

But this creates M * N strategies and gets out of hand real fast (i have about 8 operators, with 3 types with results in 24 strategies) Seeing as i want this a future proof as possible this is not the best solution IMHO.

I'm sure there must be a pattern that fixes this problem, but i'm not seeing it. (i'm no pattern hero)

Can someone help?

Sem
  • 146
  • 9
  • 1
    If your `evaluate` method always takes in `String`s as input then why do you need to compare them differently? What I am thinking is that `"3.1415".equals("3.1415")` should be the same as `((Double)3.1415).equals((Double)3.1415)` – chancea Jul 09 '13 at 14:24
  • As my example code demonstrates "40.0" wouldn't be equal to "40" whilst converting it to Doubles would. The String consideration is because it is stored in a database – Sem Jul 09 '13 at 15:14

1 Answers1

0

When I read your problem, the first thing that came into my mind is the Decorator Pattern. You could just decorate a very basic Evaluator with the required functionality for the specific use case. Still, you would have to a decent amount of decorators but if you do it right, you may end up with a pretty flexible solution.

snrlx
  • 4,987
  • 2
  • 27
  • 34
  • While it does seem decorating at first glance, it seems to me i never **add** behavior to the method, but i rather **choose** the behavior based on previous one, which doesn't seem like Decorator (adding new behavior without modifying existing) to me. Now it is a fact i ain't no pattern hero, but that's what i remember. Maybe you could elaborate your answer with a quick (even pseudo-code will suffice) example? – Sem Jul 09 '13 at 15:19