views:

435

answers:

9

Currently I have a method that acts as a factory based on a given String. For example:

public Animal createAnimal(String action)
{
    if (action.equals("Meow"))
    {
        return new Cat();
    }
    else if (action.equals("Woof"))
    {
        return new Dog();
    }

    ...
    etc.
}

What I want to do is avoid the entire if-else issue when the list of classes grows. I figure I need to have two methods, one that registers Strings to classes and another that returns the class based on the String of the action.

What's a nice way to do this in Java?

A: 

My thought would be to somehow map a String to a function. That way you can pass Meow to the map and return the constructor function that was already mapped out. I'm not sure how to do this in Java, but a quick search returned this SO thread. Someone else may have a better idea, though.

Dave McClelland
+2  A: 

I haven't tried this, but could with create a Map with "Meow", etc as keys and (say) Cat.class as value.

Provide a static instance generation via an interface and call as

Animal classes.get("Meow").getInstance()
Rui Vieira
+15  A: 

What you've done is probably the best way to go about it, until a switch on string is available.

You could create factory objects and a map from strings to these. But this does get a tad verbose in current Java.

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap =
    Collections.unmodifiableMap(new HashMap<String,AnimalFactory>() {{
        put("Meow", new AnimalFactory() { public Animal create() { return new Cat(); }});
        put("Woof", new AnimalFactory() { public Animal create() { return new Dog(); }});
    }});

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw new EhException();
    }
    return factory.create();
}

In JDK7 this may look something like:

private interface AnimalFactory {
    Animal create();
}
private static final Map<String,AnimalFactory> factoryMap = {
    "Meow" : { -> new Cat() },
    "Woof" : { -> new Dog() },
};

public Animal createAnimal(String action) {
    AnimalFactory factory = factoryMap.get(action);
    if (factory == null) {
        throw EhException();
    }
    return factory.create();
}
Tom Hawtin - tackline
Nice heads up on JDK7 :)
Rui Vieira
Whats wrong just using a Callable instead of a new AnimalFactory interface?
Thorbjørn Ravn Andersen
`Callable` throws. It's also rather non-nominative.
Tom Hawtin - tackline
Argh, double-brace initialization... (you also have 2x a typo: ` facotryMap`)
Jesper
Closures are awesome.
BalusC
@Jesper Was going to write the map initialisation in the old way, but I liked the double braces better. (Some typos fixed.)
Tom Hawtin - tackline
A: 

How about separate functions?

public class AnimalFactory
{
    public static Animal createCat()
    {
        return new Cat();
    }

    public static Animal createDog()
    {
        return new Dog();
    }
}
MatthewD
+4  A: 
crowne
Overengineered, slow, and in rather poor taste (at least the last is subjective, of course). Even in case the original problem was bigger to warrant something more sophisticated, applying a more general solution like Guice would be preferable.
Dimitris Andreou
@Dimitris, your synopsis is completely unfounded! 1) A single reusable Annotation class is preferable to having to edit a map every time you add a class, you might as well leave the if else structure in the factory if you're going to change it anyway. You could eliminate the libraries and do the reflection yourself, but I don't like to reinvent the wheel. 2) You need to quantify slow ... statements like "Reflection is slow" live in the same era as "Java is slow". 3) Even if you use Guice, you still need to somehow map the classes to an arbitrary keyword and provide a factory.
crowne
Though reflection *is* slow compared to a virtual method call (well, try it), I was referring to classpath scanning, which can be *exceptionally* slow (you *do* realize that this has to search *all* jars of the classpath, and parse part of the bytecode of *all* classes inside them - extra points if the jars are not even at the local filesystem...)
Dimitris Andreou
Ironically, this trick would incur, *just* by the dependencies you used, the cost of decompressing and parsing ~1200 classes. Let alone introducing new, silent kind of classpath-dependent bugs. Now compare all of this with the simplicity, reliability and the efficiency of some of the other answers here. Oh well, tasteful indeed! :P
Dimitris Andreou
@Dimitris, thanks for quantifying - you raise some valuable points. Although, I should point out that these haven't been left unattended by the makers of the Reflections library. They do provide means of scanning at compile time and referencing the stored results at runtime.
crowne
Was this answer a joke? I understand how this might be useful if you had 100's of types of animals, but otherwise it seems to represent everything that people complain about java and over-engineering.
GreenieMeanie
+4  A: 

There's no need for Maps with this solution. Maps are basically just a different way of doing an if/else statement anyway. Take advantage of a little reflection and it's only a few lines of code that will work for everything.

public static Animal createAnimal(String action)
{
     Animal a = (Animal)Class.forName(action).newInstance();
     return a;
}

You'll need to change your arguments from "Woof" and "Meow" to "Cat" and "Dog", but that should be easy enough to do. This avoids any "registration" of Strings with a class name in some map, and makes your code reusable for any future Animal you might add.

bluedevil2k
Generally, Class.newInstance() should be avoided (due to its poor exception handling).
Dimitris Andreou
+2  A: 

I'd look to retrieve an Enum representation of the String and switch on that.

rich
A: 

You already selected the answer to that question, but that could still help.

Although I am a .NET/C# developer, this is really a general OOP problem. I have run in the same kind of problem and I have found a nice solution (I think) using an IoC Container.

If you don't use one yet, that is probably a good reason to start. I don't know IoC containers in Java, but I assume there must be one with similar features.

What I had was a Factory that contains a reference to the IoC container, which is resolved by the container itself (in the BootStrapper)

...
public AnimalFactory(IContainer container) 
{ 
    _container = container; 
}

You can then setup your IoC container to resolve the correct types based on a key (the sound in your example). It would abstracts completely the concrete classes that your factory needs to return.

in the end, your factory method is shrunk down to this :

...
public Createable CreateAnimal(string action) 
{ 
    return _container.Resolve<Createable>(action); 
} 

This stackoverflow question illustrates the same kind of problem with real world elements and the validated answer shows a draft of my solution (pseudo code). I later wrote a blog post with the real pieces of code where it is much clearer.

Hope this can help. But it might be overkill in simple cases. I used that because I had 3 levels of dependencies to resolve, and an IoC container already assembling all my components.

Stephane
+2  A: 

If you don't have to use Strings, you could use an enum type for the actions, and define an abstract factory method.

...
public enum Action {
    MEOW {
        @Override
        public Animal getAnimal() {
            return new Cat();
        }
    },

    WOOF {
        @Override
        public Animal getAnimal() {
            return new Dog();
        }
    };

    public abstract Animal getAnimal();
}

Then you can do things like:

...
Action action = Action.MEOW;
Animal animal = action.getAnimal();
...

It's kind of funky, but it works. This way the compiler will whine if you don't define getAnimal() for every action, and you can't pass in an action that doesn't exist.

Jeff