The best way to implement Factory without if, switch

10,606

Solution 1

If you are using java 8, you can set up an enum like this:

enum AnimalFarm {
    Meow(Cat::new),
    Woof(Dog::new);

    public final Supplier<Animal> factory;
    private AnimalFarm(Supplier<Animal> factory) {
        this.factory = requireNonNull(factory);
    }
}

......

Animal dog = AnimalFarm.valueOf("Woof").factory.get();

You could even have the enum implement Supplier<Animal> and then do AnimalFarm.valueOf("Meow").get();.

Solution 2

Java 8 introduces some nice lambdas and functional interfaces that makes this really easy. It also avoids a lot of the ugly boilerplate code you would have had to write in previous Java versions. One thing that wasn't immediately clear was how to handle creating objects with constructors that take multiple arguments. Most of Java's functional interfaces allow either one argument or no arguments at all. In this case the solution is to implement your own functional interface.

Imagine your Animal classes all had constructors that looked like this:

public Dog(String name, int age) {
    // constructor stuff
}

You can create a function interface called something like Factory in your factory class that has 3 parameters. The first two parameters would be the types of objects you're going to pass to the constructor and the third will be what you're going to return.

public class AnimalFactory {

    private static final Map<String, Factory<String, Animal>> factoryMap;

    static {
        Map<String, Factory<String, Animal>> realMap = new HashMap<>();
        factoryMap = Collections.unmodifiableMap(realMap);

        realMap.put("MEOW", Cat::new);
        realMap.put("WOOF", Dog::new);
    }

    public static Animal createAnimal(String action) {
        return factoryMap.get(action).create(node);
    }

    @FunctionalInterface
    private interface Factory<T, R, S> {
        S create(T obj1, R obj2);
    } 
}

If all your classes use no-arg constructors then you can drop the custom interface and use something like Supplier instead. But everything else should more or less stay the same.

Share:
10,606
goRGon
Author by

goRGon

Senior Android Engineer

Updated on June 19, 2022

Comments

  • goRGon
    goRGon almost 2 years

    I was looking through many approaches to implement a Factory pattern in Java and still couldn't find a perfect one which doesn't suffer from both if/switch plus doesn't use reflection. One of the best that I found was inside Tom Hawtin's answer here: https://stackoverflow.com/a/3434505/1390874

    But my biggest concern is that it stores a HashMap of Anonymous classes in a memory.

    The question is what do people think about using Class.newInstance() in addition to Tom Hawtin's answer? This will avoid us from storing unnecessary anonymous classes in memory? Plus code will be more clean.

    It will look something like this:

    class MyFactory {
        private static final Map<String,Class> factoryMap =
            Collections.unmodifiableMap(new HashMap<String,Class>() {{
                put("Meow", Cat.class);
                put("Woof", Dog.class);
        }});
    
        public Animal createAnimal(String action) {
            return (Animal) factoryMap.get(action).newInstance();
        }
    }
    
    • Luiggi Mendoza
      Luiggi Mendoza about 9 years
      still couldn't find a perfect one which doesn't suffer from both if/switch plus doesn't use reflection well, you're contradicting yourself here, aren't you?
    • Radiodef
      Radiodef about 9 years
      "my biggest concern is that it stores a HashMap of Anonymous classes in a memory" Why are you concerned about this? It's generally fine. (Though subtly, the way Tom is creating the Map, the anonymous factories have an implicit reference to the Map. Something to be aware of...)
    • goRGon
      goRGon about 9 years
      @LuiggiMendoza, thank you. I thought Cat.class.newInstance() will be not reflection but seems that you are right according to: docs.oracle.com/javase/tutorial/reflect/member/…
    • goRGon
      goRGon about 9 years
      @Radiodef, the biggest concert was wasting memory.
    • Radiodef
      Radiodef about 9 years
      I wouldn't worry about that. An anonymous class transforms to a single class file associated with the expression declaring it. A normal program would have 100s or 1000s of classes already.
  • Paul Boddington
    Paul Boddington about 9 years
    Plus 1 for using an enum and no reflection, but that dog won't like being called cat!
  • Luiggi Mendoza
    Luiggi Mendoza about 9 years
    But it uses singleton, which is worse :(
  • k_g
    k_g about 9 years
    There's no singleton here. BASE_INSTANCE is not a singleton (instance() creates a new one)
  • Luiggi Mendoza
    Luiggi Mendoza about 9 years
    BASE_INSTANCE is a singleton. This is just a dirty hack to make instance a non-static method.
  • k_g
    k_g about 9 years
    @Radiodef sorry, bad copying from OP without checking to make sure that my changes created new inconsistencies...
  • goRGon
    goRGon about 9 years
    Isn't such answer for JDK7 a better approach: stackoverflow.com/a/3434505/1390874
  • goRGon
    goRGon about 9 years
    But your approach also stores a HashMap of classes in a memory. Don't we waste too much memory? Especially if classes will become bigger.
  • Radiodef
    Radiodef about 9 years
    @goRGon I believe Tom is referring to the proposed map literal feature, which doesn't exist yet.
  • k_g
    k_g about 9 years
    @goRGon the instance doesn't necessarily have to be complex. It can be completely empty.