在Java中创建一个不依赖于if-else的工厂方法

时间:2010-08-08 13:28:25

标签: java string factory

目前我有一个基于给定String充当工厂的方法。 例如:

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

    ...
    etc.
}

我想要做的是在类列表增长时避免整个if-else问题。 我想我需要有两个方法,一个将字符串注册到类,另一个根据操作的字符串返回类。

用Java做这件事的好方法是什么?

10 个答案:

答案 0 :(得分:50)

你所做的可能是最好的方法,直到有一个字符串开关可用。

您可以创建工厂对象和从字符串到这些的映射。但这在当前的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();
}

在最初编写此答案时,用于JDK7的功能可以使代码看起来如下所示。事实证明,lambdas出现在Java SE 8中,据我所知,没有地图文字的计划。

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();
}

答案 1 :(得分:13)

地图不需要此解决方案。无论如何,地图基本上只是做一个if / else语句的另一种方式。利用一点点反射,只需几行代码即可实现。

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

你需要将你的论点从“Woof”和“Meow”改为“Cat”和“Dog”,但这应该很容易做到。这避免了在某些地图中使用类名称对字符串进行“注册”,并使您的代码可以重用于您可能添加的任何未来动物。

答案 2 :(得分:12)

如果你 没有使用字符串,你可以使用枚举类型进行操作,并定义一个抽象的工厂方法。

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

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

    public abstract Animal getAnimal();
}

然后你可以做以下事情:

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

它有点时髦,但它有效。这样,如果没有为每个动作定义getAnimal(),编译器就会发出抱怨,并且你不能传入一个不存在的动作。

答案 3 :(得分:8)

使用注释!

第1步。创建如下所示的注释:

package animal;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
    String action();
}

请注意,RetentionPolicy是运行时,我们将通过反射来访问它。

第2步。(可选)创建一个公共超类:

package animal;

public abstract class Animal {

    public abstract String greet();

}

第3步。使用新注释创建子类:

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!*";
    }

}

第4步。创建工厂:

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());
    }

}

这个工厂,可以清理一下,例如处理令人讨厌的检查异常等。
在这个工厂里,我使用了Reflections库 我这样做很难,即我没有制作maven项目,我不得不手动添加依赖项 依赖关系是:

如果跳过第2步,则需要更改工厂方法以返回Object 从这一点开始,您可以继续添加子类,只要您使用AniMake(或者您提出的任何更好的名称)对它们进行注释,并将它们放在Reflection构造函数中定义的包中(在本例中为“animal”),并保持默认的no-args构造函数可见,然后工厂将为您实例化您的类,而不必自行更改。

这是输出:

log4j:WARN No appenders could be found for logger (org.reflections.Reflections).
log4j:WARN Please initialize the log4j system properly.
*WOOF!*
=^meow^=

答案 4 :(得分:5)

我没有尝试过这个,但可以创建一个带有“Meow”等的Map作为键 和(比方说)Cat.class作为价值。

通过接口提供静态实例生成,并调用

Animal classes.get("Meow").getInstance()

答案 5 :(得分:4)

我希望检索String的Enum表示并打开它。

答案 6 :(得分:1)

我的想法是以某种方式将String映射到一个函数。这样,您可以将Meow传递给地图并返回已经映射出来的构造函数。我不确定如何在Java中执行此操作,但快速搜索返回this SO thread。但是,其他人可能有更好的主意。

答案 7 :(得分:1)

您已经选择了该问题的答案,但这仍然可以提供帮助。

虽然我是.NET / C#开发人员,但这确实是一个普遍的OOP问题。我遇到了同样的问题,我找到了一个很好的解决方案(我认为)使用 IoC容器

如果你还没有使用,那可能是一个很好的理由。我不知道Java中的IoC容器,但我认为必须有一个具有相似功能的容器。

我所拥有的工厂包含对IoC容器的引用,该容器由容器本身解析(在BootStrapper中)

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

然后,您可以设置IoC容器,以根据键(示例中的声音)解析正确的类型。它将完全抽象出工厂需要返回的具体类别。

最后,您的工厂方法缩小到这个:

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

This stackoverflow question说明了与真实世界元素相同的问题,验证的答案显示了我的解决方案的草稿(伪代码)。 我后来写了a blog post with the real pieces of code,它更加清晰。

希望这可以提供帮助。但在简单的情况下可能有点过分。我之所以使用它是因为我有3个级别的依赖项要解决,而IoC容器已经组装了我的所有组件。

答案 8 :(得分:1)

人们在Tom Hawtin的回答中使用 Class.newInstance()会怎么想? 这样可以避免我们在内存中存储不必要的匿名类吗?加上代码会更干净。

看起来像这样:

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();
}

答案 9 :(得分:0)

现在您可以使用Java 8构造函数引用和功能接口。

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";
    }
}