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(\"M
Now you could use Java 8 constructor references and a functional interface.
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class AnimalFactory {
static final Map<String, Supplier<Animal>> constructorRefMap = new HashMap<>();
public static void main(String[] args) {
register("Meow", Cat::new);
register("Woof", Dog::new);
Animal a = createAnimal("Meow");
System.out.println(a.whatAmI());
}
public static void register(String action, Supplier<Animal> constructorRef) {
constructorRefMap.put(action, constructorRef);
}
public static Animal createAnimal(String action) {
return constructorRefMap.get(action).get();
}
}
interface Animal {
public String whatAmI();
}
class Dog implements Animal {
@Override
public String whatAmI() {
return "I'm a dog";
}
}
class Cat implements Animal {
@Override
public String whatAmI() {
return "I'm a cat";
}
}
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.
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()
What you've done is probably the best way to go about it, until a switch on string is available. (Edit 2019: A switch on string is available - use that.)
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();
}
At the time this answer was originally written, the features intended for JDK7 could make the code look as below. As it turned out, lambdas appeared in Java SE 8 and, as far as I am aware, there are no plans for map literals. (Edited 2016)
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();
}
Edit 2019: Currently this would look something like this.
import java.util.function.*;
import static java.util.Map.entry;
private static final Map<String,Supplier<Animal>> factoryMap = Map.of(
"Meow", Cat::new, // Alternatively: () -> new Cat()
"Woof", Dog::new // Note: No extra comma like arrays.
);
// For more than 10, use Map.ofEntries and Map.entry.
private static final Map<String,Supplier<Animal>> factoryMap2 = Map.ofEntries(
entry("Meow", Cat::new),
...
entry("Woof", Dog::new) // Note: No extra comma.
);
public Animal createAnimal(String action) {
Supplier<Animal> factory = factoryMap.get(action);
if (factory == null) {
throw EhException();
}
return factory.get();
}
If you want to add a parameter, you'll need to switch Supplier
to Factory
(and get
becomes apply
which also makes no sense in the context). For two parameters BiFunction
. More than two parameters, and you're back to trying to make it readable again.
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.
Use Scannotations!
Step 1. Create an annotation like below:
package animal;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
String action();
}
Note that the RetentionPolicy is runtime, we'll be accessing this via reflection.
Step 2. (Optional) Create a common super class:
package animal;
public abstract class Animal {
public abstract String greet();
}
Step 3. create the subclasses with your new annotation:
package animal;
@AniMake(action="Meow")
public class Cat extends Animal {
@Override
public String greet() {
return "=^meow^=";
}
}
////////////////////////////////////////////
package animal;
@AniMake(action="Woof")
public class Dog extends Animal {
@Override
public String greet() {
return "*WOOF!*";
}
}
Step 4. Create the factory:
package animal;
import java.util.Set;
import org.reflections.Reflections;
public class AnimalFactory {
public Animal createAnimal(String action) throws InstantiationException, IllegalAccessException {
Animal animal = null;
Reflections reflections = new Reflections("animal");
Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(AniMake.class);
for (Class<?> clazz : annotated) {
AniMake annoMake = clazz.getAnnotation(AniMake.class);
if (action.equals(annoMake.action())) {
animal = (Animal) clazz.newInstance();
}
}
return animal;
}
/**
* @param args
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
AnimalFactory factory = new AnimalFactory();
Animal dog = factory.createAnimal("Woof");
System.out.println(dog.greet());
Animal cat = factory.createAnimal("Meow");
System.out.println(cat.greet());
}
}
This factory, can be cleaned up a bit e.g. deal with the nasty checked exceptions etc.
In this factory, I've used the Reflections library.
I did this the hard way, i.e. I didn't make a maven project and I had to add the dependencies manually.
The dependencies are:
If you skipped Step 2, then you'll need to change the factory method to return Object.
From this point on you can keep adding subclasses, and as long as you annotating them with AniMake (or whatever better name you come up with), and place them in the package defined in the Reflections constructor (in this case "animal"), and leave the default no-args constructor visible, then the factory will instantiate your classes for you without having to be changed itself.
Here's the output:
log4j:WARN No appenders could be found for logger (org.reflections.Reflections).
log4j:WARN Please initialize the log4j system properly.
*WOOF!*
=^meow^=