6

I got a problem where I am not allowed to use switch/case or if/else queries.

I got a config file I read which is this:

650;0;1.5;month
614;0;2.88;year
466;0;2.48;week
716;0;4.6;half-year
718;0;2.6;quarter

I am splitting those Strings at the ";", so it is saved in an array. The problem I have, that I need to do other things in the code for each time given in that array ar[3], so if it is a month I need other calculations then when it is a full year.

But I am not allowed to do this with Switch/case or If/Else, now I am getting confused.

If (ar[3] = month){
do this;
else if (ar[3] = year) {
do this; 
}

How am I doing this object oriented? Thanks for every help :)

15
  • Is order of elements fixed? Like is month->year->week->...? Commented Jul 20, 2015 at 13:53
  • 2
    One word: polymorphism. You want a common interface with different implementations for each case, plus a factory to look up the one you need. Commented Jul 20, 2015 at 13:54
  • 2
    @duffymo One problem: how factory would look up without if or case? Commented Jul 20, 2015 at 13:55
  • 1
    @Pshemo, we can put the objects for each class in Map. And get it like, Map.put("month", monthClass) and then Map.get(ar[3]).performOperation() Commented Jul 20, 2015 at 13:56
  • 1
    Although that isn't to say that if or switch statement are not used in objected-oriented programming, nor does it make it "less" object-oriented. Commented Jul 20, 2015 at 14:02

5 Answers 5

4

Polymorphism by Inheritance is your friend

It seems like you need some sort of inheritance structure based on the time period in ar[3]. The special do this method could be coded for each case. That way you get the ability to do something different for each case. You just need a way to instantiate the correct subtype in the first place. There are a number of ways you could approach this.

The Conditional Operator

The most direct approach IMHO is the conditional operator, ?:.

So the code would look something like this:

MyClass x = ar[3].equals("month") ? new MyClassMonth() :
            (ar[3].equals("year") ? new MyClassYear() :
            (ar[3].equals("week") ? new MyClassWeek() :
            (ar[3].equals("half-year") ? new MyClassHalfyear() :
                                        new MyClassQuarter())));
x.doSomething();

The nested conditional expressions give you the ability to select the right class, and the inheritance gives you the polymorphic behavior you want.

But you mentioned in comment that you also can't use ?:. What next?

A Map of Stateless Objects

Suppose you wrote MyClassMonth in a way that nothing in it depended on any remembered state, i.e. the doSomething() method has no side effects. Then you could create a Map<String, MyClass> to store one instance of each subclass, then pull the relevant one out of the map when you needed to invoke.

You'd initialize the map like this:

final Map<String, MyClass> themap = new HashMap<>();
{
  themap.add("month", new MyClassMonth());
  themap.add("year", new MyClassYear());
  themap.add("week", new MyClassWeek());
  themap.add("half-year", new MyClassHalfyear());
  themap.add("quarter", new MyClassQuarter());
}

And invoke doSomething() with ar as argument:

MyClass x = themap.get(ar[3]);
if (x != null)
  x.doSomething(ar);

Other Options

There are other ways to do this. Sticking with the Map concept, you could store class literals in the Map instead of instances, then instantiate them reflectively. You could also keep a lambda in the Map and invoke it.

Enums

@OldCurmudgeon suggested using enums. If you put those enums into the Map and add a lambda to the enum, you can grab the enum and invoke the lambda. That would work and has a certain appeal, but it seems unnecessary. You'd be better off just invoking the lambda directly.

Sign up to request clarification or add additional context in comments.

4 Comments

the ? : was told to me, too. I still don't understand the example completely. One of those is possible in the end, but why am I calling new classes? Could I call something beside a class?
You stated you wanted to be "object oriented" and you tagged the question as "polymorphism". So I went with polymorphism by inheritance. The different classes give you the ability to implement doSomething() differently for the different cases without using that big switch or if-elseif-else block of code that you're trying to avoid. But if you can't use the conditional operator I'll have to revise.
I was only told to use it object oriented, I don't even know if my taskmaster knew it that way...so without calling other classes i cannot avoid switch/cases/else/if?
No, you could also use lambdas. But that's pretty advanced stuff. And at that point you're writing what I would think of as functional rather than object oriented.
3

You could use an enum as a command factory pattern and implement the choice with a Map lookup.

// Lookups for teh period.
static final Map<String, Period> lookup = new HashMap<>();

enum Period {

    Month("month") {

                @Override
                void process(int x, int y, double v) {
                    // Processing for "month" records here.
                    System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
                }
            },
    Year("year") {
                @Override
                void process(int x, int y, double v) {
                    // Processing for "year" records here.
                    System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
                }
            },
    Quarter("quarter") {
                @Override
                void process(int x, int y, double v) {
                    // Processing for "quarter" records here.
                    System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
                }
            },
    HalfYear("half-year") {
                @Override
                void process(int x, int y, double v) {
                    // Processing for "half-year" records here.
                    System.out.println(this + "-process(" + x + "," + y + "," + v + ")");
                }
            };

    Period(String inData) {
        // Record me in the map.
        lookup.put(inData, this);
    }

    abstract void process(int x, int y, double v);

    static void process(String data) {
        String[] parts = data.split(";");
        Period p = lookup.get(parts[3]);
        if (p != null) {
            p.process(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Double.parseDouble(parts[2]));
        }
    }
}

public void test() {
    String[] test = {"650;0;1.5;month",
        "614;0;2.88;year",
        "466;0;2.48;week",
        "716;0;4.6;half-year",
        "718;0;2.6;quarter",};
    for (String s : test) {
        Period.process(s);
    }
}

correctly prints:

Month-process(650,0,1.5)
Year-process(614,0,2.88)
HalfYear-process(716,0,4.6)
Quarter-process(718,0,2.6)

Note that there is one if in there but that is only defensive to avoid bad data - it is not part of the lookup mechanism.

Comments

2

Something like this:

public interface Calculator {
    double calculate(int p1, int p2, double p3);
}

public class YearCalculator implements Calculator {
    public double calculate(int p1, int p2, double p3) {
        double value = 0.0;
        // do year calculations
        return value;
    }
}

public class CalculatorFactory {
    public Calculator getInstance(String type) {
       Calculator calculator = null;
       if (type != null) {
       } else {
           throw new IllegalArgumentException("calculator type cannot be null");
           if ("year".equalsIgnoreCase(type)) {
           } else {
               System.out.println(String.format("No such type: %s", type));
           }
       }
       return calculator;
    }
}

You have to have if/else logic in the factory, but not when you're parsing the text.

Your processing code:

CalculatorFactory factory = new CalculatorFactory();
// contents is a List of Strings from your input file.
for (String line : contents) {
    String [] tokens = line.split(";");
    Calculator calculator = factory.getInstance(tokens[3]);
    double value = calculator.calculate(Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1]), Double.parseDouble(tokens[2]));
}

Comments

2

Building upon the suggestion given by Codebender as an alternative solution:

You need 5 classes, one for each case, with a common interface but different implementations.

Your interface may look something like this:

public interface MyCalculator {

    public double calculate(double a, double b, double c);

}

Then you will need to implement your 5 classes similar to this. You will need a different class with a different implementation for calculate for month, year, week, half-year and quarter:

public class MyMonthCalculator implements MyCalculator {

    @Override
    public double calculate(double a, double b, double c) {
        // Do your calculations here then return
    }

}

Then, before your parsing logic, you can add the five classes to a Map.

map.put("month", new MyMonthCalculator());
// Repeat for year, week, half-year and quarter

To actually perform a calculation:

double result = map.get(ar[3]).calculate(Double.parseDouble(ar[0]), Double.parseDouble(ar[1]), Double.parseDouble(ar[2]));

Comments

0

You can simulate if or case with arrays of options. Only problem here would be finding index of our element in such array. We can't use if and case but I assume that while is an option.

So your code can be similar to something like:

String[] options = { "foo", "bar", "baz" };
Runnable[] action = { new Runnable() {
    @Override
    public void run() {
        System.out.println("handling foo");
    }
}, new Runnable() {
    @Override
    public void run() {
        System.out.println("handling bar");
    }
}, new Runnable() {
    @Override
    public void run() {
        System.out.println("handling baz");
    }
} };
String choice = "bar";
int matched = 0;
int i = -1;
while (matched != 1) {
    i++;
    matched = boolToInt(options[i].equals(choice));
}
action[i].run();

I used method like this to convert boolean to integer where 1=true, 0=false

public static int boolToInt(Boolean b) {
    return 5 - b.toString().length();
}

Instead Runnable you can provide your own interface.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.