tl;dr 我建议使用抽象工厂模式。
长答案:
为了比较这些方法,我在下面附上了四种可能的解决方案。总结如下:
- 使用抽象工厂模式
- 使用用户直接选择的字符串按名称实例化类
- 获取用户直接选择的字符串并将其转换为另一个字符串以按名称实例化一个类
- 获取用户直接选择的字符串并将其转换为 Class 对象以实例化该类
比较
使用Class::forName
首先,反射解决方案 2 和 3 使用提供类名的 String 来标识类对象。这样做很糟糕,因为它破坏了自动重构工具:当你重命名类时,字符串不会改变。此外,不会有编译器错误。错误只会在运行时可见。
请注意,这并不取决于重构工具的质量:在解决方案 2 中,提供类名的 String 可能以您能想到的最隐晦的方式构造。它甚至可能由用户输入或从文件中读取。重构工具无法完全解决这个问题。
解决方案 1 和 4 没有这些问题,因为它们直接链接到类。
GUI 与类名的耦合
由于解决方案 2 直接使用用户提供的 String 进行反射以按名称识别类,因此 GUI 与您在代码中使用的类名称耦合。这很糟糕,因为这需要您在重命名类时更改 GUI。重命名类应始终尽可能简单,以实现轻松重构。
解决方案 1、3 和 4 不存在此问题,因为它们会将 GUI 使用的字符串转换为其他内容。
流量控制的例外情况
解决方案 2、3 和 4 在使用反射方法 forName 和 newInstance 时必须处理异常。解决方案 2 甚至必须使用异常进行流控制,因为它没有任何其他方法来检查输入是否有效。使用异常进行流控制通常被认为是不好的做法。
解决方案 1 没有这个问题,因为它不使用反射。
反射的安全问题
方案二直接使用用户提供的String进行反射。这可能是一个安全问题。
解决方案 1、3 和 4 不存在此问题,因为它们会将用户提供的字符串转换为其他内容。
使用特殊类加载器进行反射
您无法在所有环境中轻松使用这种类型的反射。例如,您在使用 OSGi 时可能会遇到问题。
解决方案 1 没有这个问题,因为它不使用反射。
带参数的构造函数
给定的示例仍然很简单,因为它没有使用构造函数参数。在构造函数参数中使用类似的模式是很常见的。解决方案 2、3 和 4 在这种情况下变得丑陋,请参阅Can I use Class.newInstance() with constructor arguments?
解决方案 1 只需将 Supplier 更改为与构造函数签名匹配的功能接口。
使用工厂(方法)创建复杂的水果
解决方案 2、3 和 4 要求您通过构造函数实例化水果。但是,这可能是不可取的,因为您通常不希望将复杂的初始化逻辑放入构造函数中,而是放入工厂(方法)中。
解决方案 1 没有这个问题,因为它允许您将任何创建水果的函数放入地图中。
代码复杂度
以下是引入代码复杂性的元素,以及它们出现的解决方案:
- 在 1、3 和 4 中创建地图
- 2、3 和 4 中的异常处理
上面已经讨论过异常处理。
地图是将用户提供的字符串转换为其他内容的代码部分。因此,地图解决了上述许多问题,这意味着它是有目的的。
请注意,映射也可以替换为List 或数组。但是,这不会改变上述任何结论。
代码
通用代码
public interface Fruit {
public static void printOptional(Optional<Fruit> optionalFruit) {
if (optionalFruit.isPresent()) {
String color = optionalFruit.get().getColor();
System.out.println("The fruit is " + color + ".");
} else {
System.out.println("unknown fruit");
}
}
String getColor();
}
public class Apple implements Fruit {
@Override
public String getColor() {
return "red";
}
}
public class Banana implements Fruit {
@Override
public String getColor() {
return "yellow";
}
}
抽象工厂(一)
public class AbstractFactory {
public static void main(String[] args) {
// this needs to be executed only once
Map<String, Supplier<Fruit>> map = createMap();
// prints "The fruit is red."
Fruit.printOptional(create(map, "apple"));
// prints "The fruit is yellow."
Fruit.printOptional(create(map, "banana"));
}
private static Map<String, Supplier<Fruit>> createMap() {
Map<String, Supplier<Fruit>> result = new HashMap<>();
result.put("apple", Apple::new);
result.put("banana", Banana::new);
return result;
}
private static Optional<Fruit> create(
Map<String, Supplier<Fruit>> map, String userChoice) {
return Optional.ofNullable(map.get(userChoice))
.map(Supplier::get);
}
}
反射(2)
public class Reflection {
public static void main(String[] args) {
// prints "The fruit is red."
Fruit.printOptional(create("stackoverflow.fruit.Apple"));
// prints "The fruit is yellow."
Fruit.printOptional(create("stackoverflow.fruit.Banana"));
}
private static Optional<Fruit> create(String userChoice) {
try {
return Optional.of((Fruit) Class.forName(userChoice).newInstance());
} catch (InstantiationException
| IllegalAccessException
| ClassNotFoundException e) {
return Optional.empty();
}
}
}
用地图反射 (3)
public class ReflectionWithMap {
public static void main(String[] args) {
// this needs to be executed only once
Map<String, String> map = createMap();
// prints "The fruit is red."
Fruit.printOptional(create(map, "apple"));
// prints "The fruit is yellow."
Fruit.printOptional(create(map, "banana"));
}
private static Map<String, String> createMap() {
Map<String, String> result = new HashMap<>();
result.put("apple", "stackoverflow.fruit.Apple");
result.put("banana", "stackoverflow.fruit.Banana");
return result;
}
private static Optional<Fruit> create(
Map<String, String> map, String userChoice) {
return Optional.ofNullable(map.get(userChoice))
.flatMap(ReflectionWithMap::instantiate);
}
private static Optional<Fruit> instantiate(String userChoice) {
try {
return Optional.of((Fruit) Class.forName(userChoice).newInstance());
} catch (InstantiationException
| IllegalAccessException
| ClassNotFoundException e) {
return Optional.empty();
}
}
}
类映射反射 (4)
public class ReflectionWithClassMap {
public static void main(String[] args) {
// this needs to be executed only once
Map<String, Class<? extends Fruit>> map = createMap();
// prints "The fruit is red."
Fruit.printOptional(create(map, "apple"));
// prints "The fruit is yellow."
Fruit.printOptional(create(map, "banana"));
}
private static Map<String, Class<? extends Fruit>> createMap() {
Map<String, Class<? extends Fruit>> result = new HashMap<>();
result.put("apple", Apple.class);
result.put("banana", Banana.class);
return result;
}
private static Optional<Fruit> create(
Map<String, Class<? extends Fruit>> map, String userChoice) {
return Optional.ofNullable(map.get(userChoice))
.flatMap(ReflectionWithClassMap::instantiate);
}
private static Optional<Fruit> instantiate(Class<? extends Fruit> c) {
try {
return Optional.of(c.newInstance());
} catch (InstantiationException
| IllegalAccessException e) {
return Optional.empty();
}
}
}