1

If I have a program that does the following:

if(input=='abc'){do x}
if(input=='def'){do y}

In the future, I may want to add another piece of code like so:

if(input=='ghy'){do x}

As you can see, I am adding a new 'if' statement for a different conditional BUT using the SAME function X. The code in future has potential to have lots of different IF statements (or switches) all of which are comparing a string vs a string and then performing a function. Considering the future expansion, I was wondering if there is a possible 'neater', 'modular' way of achieving the same results.

It's a shame I can't combine the String with a Method call in a hashtable (String, method) in Java. That way I could just store any new procedures inside a hashtable and grab the relevant method for that String.

Any ideas?

Thank you

EDIT: Thank you for everyone's solutions. I was surprised by the quantity and quality of replies I received in such a small amount of time.

guesswork
  • 41
  • 1
  • 1
  • 3
  • I second the consideration of using Map. One nitpick though, if you want to use a String in a boolean expression, then use the equals or equalsIgnoreCase method, not ==. And of course in Java Strings use double quotes. – Hovercraft Full Of Eels Feb 20 '11 at 14:58
  • If you did this could you have one interface listing all methods, or would you need an interface for each method? – guesswork Feb 20 '11 at 15:01
  • No, only one interface should be used. Google the "command design pattern" because I think that this is what you are looking for. – Hovercraft Full Of Eels Feb 20 '11 at 15:05
  • @Hover,@johusman just a thought , in future if any hash collission happens will wrong method get called ? any chance of happening. – Dead Programmer Feb 20 '11 at 15:06
  • That's what the equals method is for. If the hashCodes are the same, then equals should sort out the different objects. If both hashCode are the same and equal returns true, then it's not a collision as the two Strings are effectively one and the same. – Hovercraft Full Of Eels Feb 20 '11 at 15:13
  • I was just playing around with this and unsure if it is possible? I should have mentioned in my opening post - I often need to pass parameters in the methods being called. – guesswork Feb 20 '11 at 15:21

4 Answers4

4

Maybe you can use enum. Example:

public enum InputType
{

    abc, def
    {
        @Override
        public void x()
        {
            System.out.println("Another method");
        }
    },
    ghy;

    public void x()
    {
        System.out.println("One method");
    }
}

And further:

InputType.valueOf("abc").x();

Cheers!

Lachezar Balev
  • 11,498
  • 9
  • 49
  • 72
  • Just mind that it gets tricky if your strings contain spaces or special characters. But even though enum will be a reasonable choice with little modifications. Cheers! – Lachezar Balev Feb 20 '11 at 15:12
  • Hmm the strings may on multiple occasions contains spaces. What is the best way to get around this? – guesswork Feb 20 '11 at 15:19
  • @lucho why semicolon comes after the ghy, is there any syntax to call method for default – Dead Programmer Feb 20 '11 at 15:21
  • @guesswork - the best way is to provide a lookup method that will be used instead of valueOf(). You may try to implement such a method after reading this interesting thread: http://stackoverflow.com/questions/5021246/conveniently-map-between-enum-and-int-string/5021672#5021672 – Lachezar Balev Feb 20 '11 at 15:36
  • @Suresh S The semicolon comes after the last constant into the enumeration. The `default` method is this one after `ghy`. It is invoked on any constant except those that override the method explicitly (`def`) – Lachezar Balev Feb 20 '11 at 15:39
1

I guess you could always use a Map<String, Runnable> and map to anonymous Runnable implementations:

myMap.put("abc", new Runnable() { public void run() { do x } });

...

myMap.get(input).run();
johusman
  • 3,472
  • 1
  • 17
  • 11
  • Almost as horrible as using `java.lang.reflect.Method`. – Johan Sjöberg Feb 20 '11 at 14:57
  • Ah, thats an idea. But what if each of these 'methods' are in one class. IE: Foo.x(), Foo.y(), Foo.z(). I haven't used Runnables much apart from in Threads so unsure if this is achievable. – guesswork Feb 20 '11 at 14:59
  • You _could_ create an interface with a method `op()`, then an implementation class for each operation `x`, `y`. etc. Then associate the desired implementation with each string. Run it by: `map.get("abc").op()`; – Johan Sjöberg Feb 20 '11 at 15:02
  • I'm not sure exactly what you mean, but two things: Runnable is just an interface with a run method; apart from being used by Threads, it doesn't have any special threading significance. Secondly, the anonymous Runnable in this case is just boilerplate, a way to execute the code you want to execute at the given time. It can call any method it has access to (methods and fields of the surrounding class, global stuff, plus any `final` variables in the surrounding method, but of course it cannot access variables that have not been defined yet at the time you define the anonymous Runnables. – johusman Feb 20 '11 at 15:06
1

You should take a look at the command pattern. There are several ways of implementing it, and frameworks such as Spring can help you do with in a clean way.

But in a simple manner here's what you could do:

1-Create a Command interface with a method that your program will have to call to do the task, say doTask()

2-Create classes for command X and Y, implementing the Command interface.

3-Create a Map<String, Command> that will map your commands (X and Y) to logical names

4-Create a configuration file of your choice, say a .properties file that will map your input to your command names: abc=X, def=Y, ghi=X

5-Your program then does lookups on the config file to know which command to run according to the input.

svachon
  • 7,666
  • 1
  • 18
  • 16
  • Is it possible to do this if the Command methods require parameters? – guesswork Feb 20 '11 at 15:24
  • Yes, several ways to do it but you could simply pass a Map for passing parameters. Your command would know how to deal with it. – svachon Feb 20 '11 at 15:30
  • I must be oblvious to how this works.. sorry. Say if I have an Interface FooInterface with a method x(String string), implemented by the class Foo. I create a Map but the only thing I can do now is put("abc", new Foo().x("String y")). But what if at the point of populating the map I don't know what I want "String y" to be? What if I want to pass "String y" at the time of calling? Thank you – guesswork Feb 20 '11 at 15:37
  • the doTask() method should be doTask(Map params) – svachon Feb 20 '11 at 15:41
0

A lot of ifs always tell us that we could do this better. In your case better option is to use design pattern e.g. Chain of responsibility. You will have good implementation which you can dynamic change and your code will be easier to maintenance than ifs implementation.

Take a look at this adaptation chain of responsibility to your case:

Main:

public static void main(String[] args) {
    ClassA classA = new ClassA(Arrays.asList("abc", "ghi"));
    ClassB classB = new ClassB(Arrays.asList("def"));
    classA.setNextInChain(classB);  // you can always write Builder to do this
    String input = "def";
    classA.execute(input);
}

BaseClass:

public abstract class BaseClass {
    private Collection<String> patterns = Collections.EMPTY_LIST;
    protected BaseClass nextInChain;
    protected abstract void doMethod();  // your doA, doB methods

    public void execute(String input) {
            // this replace many ifs in your previous implementation
        if (patterns.contains(input)) {
            doMethod();
        } else {
            nextInChain.execute(input);
        }       
    }

    public void setPatterns(Collection<String> patterns) {
        this.patterns = patterns;
    }

    public void setNextInChain(BaseClass nextInChain) {
        this.nextInChain = nextInChain;
    }
}

Class in chain:

public class ClassA extends BaseClass {
    ClassA(Collection<String> patterns) {
        setPatterns(patterns);
    }
    @Override
    protected void doMethod() {
        // do A     
    }
}

public class ClassB extends BaseClass {...}
lukastymo
  • 26,145
  • 14
  • 53
  • 66