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

Bra*_*rad 38 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.
}
Run Code Online (Sandbox Code Playgroud)

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

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

Tom*_*ine 51

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

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

在最初编写此答案时,用于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();
}
Run Code Online (Sandbox Code Playgroud)

  • 很高兴JDK7 :) (4认同)
  • 仅使用Callable而不是新的AnimalFactory接口有什么不对? (3认同)
  • *"在JDK7中,这可能看起来像......"*你应该为Java 8更新它.) (2认同)

blu*_*l2k 13

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

public static Animal createAnimal(String action)
{
     Animal a = (Animal)Class.forName(action).newInstance();
     return a;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 通常,应避免使用Class.newInstance()(由于其异常处理不当). (5认同)

小智 12

如果你不具备使用字符串,你可以使用一个枚举类型的行为,并定义一个抽象的工厂方法.

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

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

    public abstract Animal getAnimal();
}
Run Code Online (Sandbox Code Playgroud)

然后你可以做以下事情:

...
Action action = Action.MEOW;
Animal animal = action.getAnimal();
...
Run Code Online (Sandbox Code Playgroud)

这有点时髦,但它确实有效.这样,如果您没有为每个操作定义getAnimal(),并且您无法传入不存在的操作,编译器将发出抱怨.


cro*_*wne 8

使用Scannotations!

步骤1. 创建如下注释:

package animal;

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

@Retention(RetentionPolicy.RUNTIME)
public @interface AniMake {
    String action();
}
Run Code Online (Sandbox Code Playgroud)

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

步骤2.(可选)创建一个公共超类:

package animal;

public abstract class Animal {

    public abstract String greet();

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

这个工厂,可以清理一下,例如处理讨厌的检查异常等.
在这个工厂里,我使用了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^=
Run Code Online (Sandbox Code Playgroud)

  • 过度工作,缓慢,味道相当差(当然至少最后一个是主观的).即使最初的问题更大,以保证更复杂的东西,应用像Guice这样的更通用的解决方案也会更好. (6认同)
  • 这个回答是个笑话吗?我理解如果你有100种动物,这可能会有用,但除此之外它似乎代表了人们抱怨java和过度工程的一切. (4认同)
  • @Dimitris,你的故事大纲是完全没有根据的!1)单个可重用的Annotation类比每次添加类时都必须编辑一个地图更好,如果你要改变它,你也可以在工厂中留下if else结构.你可以消除库并自己做反射,但我不喜欢重新发明轮子.2)你需要在与"Java慢"相同的时代量化慢速......类似"反射很慢"的陈述.3)即使你使用Guice,你仍然需要以某种方式将类映射到任意关键字并提供工厂. (2认同)
  • 具有讽刺意味的是,这个技巧只会由你使用的依赖项,解压缩和解析~1200类的成本引起.更不用说引入新的,沉默的类路径依赖的bug.现在将所有这些与这里的一些其他答案的简单性,可靠性和效率进行比较.哦,确实很有品味!:P (2认同)

Rui*_*ira 5

我没有试过这个,但可以创建一个Map"喵"等作为键和(说)Cat.class作为值.

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

Animal classes.get("Meow").getInstance()
Run Code Online (Sandbox Code Playgroud)