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

2022-09-01 00:49:47

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

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中做到这一点的好方法是什么?


答案 1

你所做的可能是最好的方法,直到字符串上的开关可用。(编辑2019:字符串上的开关可用 - 使用它。

您可以创建工厂对象以及从字符串到这些对象的映射。但这在当前的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中,据我所知,没有映射文字的计划。(编辑于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();
}

编辑2019年:目前,这看起来像这样。

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

如果要添加参数,则需要切换到(并成为在上下文中也没有意义)。对于两个参数。超过两个参数,然后您又开始尝试使其再次可读。SupplierFactorygetapplyBiFunction


答案 2

使用此解决方案不需要地图。无论如何,地图基本上只是执行if/else语句的另一种方式。利用一点点的反射,只有几行代码就可以适用于所有事情。

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

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