2

I have a code which looks something like this:

if(num == 1) {
return new Alg1();
} else if (num == 2) {
return new Alg2();
}
...
else if (num == n) {
return new AlgN();
}

I have tried to use the Strategy pattern but it seems like it does not satisfy the task to reduce the if statements, can you please suggest me some other way to do it, thanks

Tano
  • 1,285
  • 1
  • 18
  • 38
  • 1
    use Switch ... case: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/switch.html – Jens Jul 12 '16 at 09:14

3 Answers3

6

You can use reflection

try {
    return Class.forName("mypackage.Alg" + num).newInstance();
} catch (Exception e) { 
    // handle exception
}

You could chose to wrap with a RuntimeException or not wrap it if it was one you were expecting like

public static Algorythm loadAlgo(int n) throws IOException {
    try {
        return Class.forName("mypackage.Alg" + num).newInstance();

    } catch (Exception e) { 
        if (e instanceof IOException) 
            throw (IOException) e;
        throw new IOException("Unable to load Algo", e);
    }

You have to catch all exceptions not just checked ones with this newInstance() method as it doesn't wrap exceptions thrown in the constructor. You could use the longer

try {
    return Class.forName("mypackage.Alg" + num).getConstructor().newInstance();
} catch (Exception e) { 
    // handle exception
}

However, it doesn't make much difference in this case except exception thrown will be wrapped and you might have to unwrap them to see the original.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Do not use Class.newInstance() use Class.getConstructor().newInstance() instead (Class.newInstance() does not have sane exception scheme). Typical overuse of reflection. – k5_ Jul 12 '16 at 09:30
  • @k5_ I haven't mentioned how exception handling should work, I will add it to my answer. – Peter Lawrey Jul 12 '16 at 09:35
  • You seriously think exception handling should look like that? – k5_ Jul 12 '16 at 09:38
  • @k5_ I would happy for you to provide a complete alternative answer i.e. not one with a "..." I will let you fill in all the repetitive code here bit. – Peter Lawrey Jul 12 '16 at 09:38
  • @k5_ I welcome a cleaner, simpler suggestion. – Peter Lawrey Jul 12 '16 at 09:39
  • The problem with your first Class.newInstance() approach was that you would have swollowed everything that came from calling the constructor of AlgX(), almost no option to recover from that. My appraoch would be to catch InvocationTargetException and rethrow the targetexception leading to a "throws Throwable" sucks but at least something. http://stackoverflow.com/questions/195321/why-is-class-newinstance-evil – k5_ Jul 12 '16 at 09:57
  • @k5_ if you wanted to rethrow you could use a RuntimeException of your choice. – Peter Lawrey Jul 12 '16 at 10:31
  • As Joshua Bloch said in his "Effective Java" - "prefer annotations to naming conventions", it will make your code more robust and clean. – Andrei_N Jul 12 '16 at 20:48
  • @Andrei_N I agree if there is a standard annotation which all libraries recognise. If you use an annotation only one library recognises, you are locking yourself into using that library. – Peter Lawrey Jul 13 '16 at 07:44
6

Unless you're going to provide 4 billion Alg* classes, use an enum instead of an int:

enum Strategy {
  ALG_1 { @Override public Alg1 newInstance() { return new Alg1(); },
  ALG_2 { @Override public Alg2 newInstance() { return new Alg2(); },
  // ...
  ;

  public abstract AlgBase newInstance();
}

Then there is no need for any conditionals:

return strategy.newInstance();

where strategy is the instance of Strategy.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
3

If you want to dynamically add new algorithms you might want to take a look at spi. https://docs.oracle.com/javase/tutorial/ext/basics/spi.html. These approaches mean that all algorithms must be initialisable, if one fails to construct none is usable.

With a textfile you register implementations of an interface and resolve these implementations by:

    ServiceLoader<Algorithm> algorithms = ServiceLoader.load(Algorithm.class);
    for (Algorithm algorithm : algorithms) {
        if (algorithm.getId().equals(num)) {
            return algorithm;
        }
    }
    throw new IllegalArgumentException("Algorithm with num " + num + " does not exist");

That will return an Iterable for all implementations of that algorithm. You can loop through them to find the correct one.

You might want to store/cache the algorithms in a Map if you resolve them often in one run of your application.

This map can also be manually created if you dont want to use spi.

private static final Map<Integer, Algorithm> ALGORITHMS;

static {
    Map<Integer, Algorithm> algs = new HashMap<>();
    algs.put(1, new Alg1());
    algs.put(2, new Alg2());
    algs.put(3, new Alg3());
    ALGORITHMS = Collections.unmodifiableMap(algs);
}
k5_
  • 5,450
  • 2
  • 19
  • 27