Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a factory method in Java that doesn't rely on if-else

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?

like image 744
Brad Avatar asked Aug 08 '10 13:08

Brad


People also ask

Which are the three types of factory method?

the abstract factory pattern,the static factory method, the simple factory (also called factory).

What are the factory methods in Java How do you create a factory method?

Factory method is a creational design pattern which solves the problem of creating product objects without specifying their concrete classes. The Factory Method defines a method, which should be used for creating objects instead of using a direct constructor call ( new operator).

What can be the disadvantages of factory method?

A potential disadvantage of Factory methods is that clients might have to sub-class the creator class just to create a particular concrete product object. Subclassing is fine when the client has to subclass the creator class anyway, but otherwise, the client now must deal with another point of evolution.

What is the benefit of using a factory class rather than just creating an object directly with new?

The advantage of a Factory Method is that it can return the same instance multiple times, or can return a subclass rather than an object of that exact type.


2 Answers

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.

like image 85
Tom Hawtin - tackline Avatar answered Oct 09 '22 05:10

Tom Hawtin - tackline


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.

like image 28
bluedevil2k Avatar answered Oct 09 '22 05:10

bluedevil2k