1

I have an interface called Command:

public interface Command {
// irrelevant code
}

And about 15 classes that implement this interface:

public class Comm_A implements Command {
// irrelevant code
}

public class Comm_B implements Command {...}
....
public class Comm_XZ implements Command {...}

And the factory which should take a String and return an instance of the corresponding command:

public class CommandFactory {
    public static Command getCommand(String s) {
        if (s.equals("Comm_A")) return new Comm_A();
        else ... // each particular case
    }
}

Consider that I want to be able to add new Commands without changing the factory code.

Can I make it to automatically try and create the corresponding instance WITHOUT hard-coding each "Comm_A"..."Comm_XZ" case and WITHOUT using java.lang.reflect?

Antti Degl
  • 69
  • 8
  • Can you call 'getCommand()' with a class instead of a String? – Florian Nov 25 '15 at 13:48
  • No, the string is read from a JTextPane and manually entered. – Antti Degl Nov 25 '15 at 13:49
  • 1
    Why don't you want to use reflect? While I agree that it is usually wise to stay away of it while you do not need it (and avoid "clever" tricks), this use case is perfect for it. – SJuan76 Nov 25 '15 at 13:51

3 Answers3

1

generate the fully qualified name (package.class) and call

Class<?> myClass = Class.forName(className);

Then call

myClass.newInstance();
Shloim
  • 5,281
  • 21
  • 36
1

If your commands are stateless, thread-safe objects, you can create a map of eagerly-created instances and use these instances repeatedly:

public class CommandFactory {
    private final Map<String, Command> availableCommands;

    public CommandFactory(Map<String, Command> availableCommands) {
        this.availableCommands = availableCommands;
    }

    public Command getCommand(String s) {
        if (availableCommands.contains(s) {
            return availableCommands.get(s);
        } else {
            // handle error state
        }
    }
}

For this to be any good (and fulfill the "no changes to the class" requirement) however the factory would not be a static class, with instance (rather than static) getCommand() method. The available commands then can be injected using a DI framework.


If you need to create a new instance of the Command implementation for each call, then you cannot really avoid reflection without using the terrible if...else if chain. I wouldn't be too worried about using it in this case though, Class.newInstance() is quite readable and not terribly inefficient.

I would still stick with that map approach though even for this, to make it flexible and extendable by configuration. The code would be similar just with Map<String, Class>, return availableCommands.get(s).newInstance() and some more exception checking.


A third possible approach is creating a separate factory class for each command type, and having a Map<String, SpecificCommandFactory>, where you'd query for the appropriate factory and then use that factory to get a new instance of that specific command class - but this is a lot of boilerplate code and can be quite unreadable, so is only suitable if you really need to separate the commands available from the factory:

public iterface SpecificCommandFactory {
    Command createCommand();
}

public class Comm_AFactory implements SpecificCommandFactory {
    public Comm_A createCommand() {
        return new Comm_A();
    }
}

public class CommandFactory {
    private final Map<String, SpecificCommandFactory> availableCommands;

    public CommandFactory(Map<String, Command> availableCommands) {
        this.availableCommands = availableCommands;
    }

    public Command getCommand(String s) {
        if (availableCommands.contains(s) {
            return availableCommands.get(s).createCommand();
        } else {
            // handle error state
        }
    }
}
Jiri Tousek
  • 12,211
  • 5
  • 29
  • 43
0

You could use some sort of delegating factory for this. Instead of holding references to the objects in a map (as in Jiri Tousek's answer) you could hold factories for the different commands:

public class CommandFactory{
    private static final Map<String, Callable<Command>> commands = new HashMap<>();
    public static void registerCommand(String command, Callable<Command> factory){
        commands.put(command, factory);
    }
    public static Command getCommand(String command){
        Callable<Command> factory = commands.get(command);
        if(factory != null){
            return factory.call();
        }
        return null;
    }
}
Jiri Tousek
  • 12,211
  • 5
  • 29
  • 43
t_over
  • 122
  • 4