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

目前我有一个方法,作为基于给定的string的工厂。 例如:

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

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

在Java中这样做的好方法是什么?

你所做的事可能是最好的方法,直到开关string可用。

您可以创build工厂对象和从这些string到地图。 但是在目前的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的特性可能会使代码如下所示。 事实certificate,lambda出现在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(); } 

这个解决scheme不需要Maps。 无论如何,映射基本上只是一个不同的方法来执行if / else语句。 利用一点反思,这只是几行代码将适用于一切。

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

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

如果不必使用string,则可以使用枚举types作为操作,并定义一个抽象工厂方法。

 ... 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(),编译器就会抱怨,你不能传入一个不存在的动作。

使用Scannotations!

第1步。创build一个注释,如下所示:

 package animal; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface AniMake { String action(); } 

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

第2步。 (可选)创build一个通用的超类:

 package animal; public abstract class Animal { public abstract String greet(); } 

第3步。使用新的批注创build子类:

 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步。创build工厂:

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

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

  • reflection-0.9.5-RC2.jar
  • 谷歌的集合-1.0.jar
  • SLF4J-API-1.5.6.jar
  • nlog4j-1.2.25.jar
  • Javassist进行-3.8.0.GA.jar
  • dom4j的-1.6.jar

如果您跳过了第2步,那么您需要将工厂方法更改为返回Object。
从这一点开始,你可以继续添加子类,只要你用AniMake(或者你想出的任何更好的名字)注释它们,并把它们放在Reflections构造函数中定义的包中(在本例中为“animal”),并保持默认的无参数构造函数可见,那么工厂将为您实例化您的类,而不必自行更改。

这是输出:

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

我没有试过这个,但可以创build一个与“喵”等Map作为键和(说) Cat.class作为价值。

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

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

我希望检索一个string的枚举表示,并打开它。

我的想法是以某种方式将一个string映射到一个函数。 这样,您可以将Meow传递给映射并返回已映射出的构造函数。 我不知道如何在Java中做到这一点,但快速search返回这个SO线程 。 不过别人可能有更好的主意。

你已经select了这个问题的答案,但这仍然有帮助。

尽pipe我是.NET / C#开发人员,但这确实是一个普遍的OOP问题。 我已经运行在同样的问题,我已经find了一个很好的解决scheme(我认为)使用IoC容器

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

我所拥有的是一个工厂,它包含对IoC容器的引用,由容器本身parsing(在BootStrapper中)

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

然后,您可以设置您的IoC容器来解决基于键(在您的示例中的声音)的正确types。 它会完全抽象你的工厂需要返回的具体类。

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

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

这个stackoverflow问题说明与现实世界的元素相同types的问题和validation答案显示我的解决scheme(伪代码)的草稿。 后来我写了一篇博客文章 ,里面有更清晰的代码 。

希望这可以帮助。 但是在简单情况下可能会过度。 我之所以这样做,是因为我有3个级别的依赖关系需要解决,而且IoC容器已经集合了我所有的组件。

那么在Tom Hawtin的答案中,人们如何使用Class.newInstance()呢? 这将避免我们在内存中存储不必要的匿名类? Plus代码会更干净。

它看起来像这样:

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