我应该如何根据用户选择选择应该实例化哪个具体实现?
我有一个接口Fruit
有两个实现Apple
和Banana
。 我想创建一个Fruit
实例。 应该由用户选择具体实现应该是Apple
还是Banana
。 我还没有设计用户界面,所以用户做出这个选择没有限制。
我知道有以下选择:
- 使用抽象工厂模式
- 使用reflection从给定的类名创建实例
- 使用reflection从给定的类对象创建实例
这些选项的优缺点是什么?
请注意,虽然有几个类似的问题讨论了一种或另一种方法,但我没有找到一个比较。
以下是相关问题列表:
- 为什么Class.newInstance()“邪恶”?
- 我可以将Class.newInstance()与构造函数参数一起使用吗?
- 如何制作类的ArrayList?
- Class.newInstance()是否遵循“抽象工厂”设计模式?
tl; dr我建议使用抽象工厂模式。
答案很长:
为了比较这些方法,我在下面附上了四种可能的解决方 以下是摘要:
- 使用抽象工厂模式
- 使用由用户直接选择的String来按名称实例化一个类
- 获取由用户直接选择的String,并将其转换为另一个String,以按名称实例化一个类
- 获取由用户直接选择的String,并将其转换为Class对象以实例化该类
对照
使用Class::forName
首先,reflection解决方案2和3使用提供类名的String来标识类对象。 这样做很糟糕,因为它会破坏自动重构工具:重命名类时,不会更改String。 此外,不会有编译器错误。 该错误仅在运行时可见。
请注意,这不取决于重构工具的质量:在解决方案2中,提供类名的String可能以您能想到的最隐蔽的方式构造。 它甚至可以由用户输入或从文件中读取。 重构工具无法完全解决此问题。
解决方案1和4没有这些问题,因为它们直接链接到类。
GUI与类名的耦合
由于解决方案2直接使用用户提供的String进行reflection以按名称标识类,因此GUI将耦合到您在代码中使用的类名。 这很糟糕,因为这要求您在重命名类时更改GUI。 重命名类应该始终尽可能简单,以便轻松重构。
解决方案1,3和4没有这个问题,因为它们将GUI使用的String转换为其他内容。
流量控制的例外情况
当使用forName
和newInstance
的reflection方法时,解决方案2,3和4必须处理exception。 解决方案2甚至必须使用流控制的exception,因为它没有任何其他方法来检查输入是否有效。 使用流量控制的exception通常被认为是不好的做法。
解决方案1没有此问题,因为它不使用reflection。
反思的安全问题
解决方案2直接使用用户提供的String进行reflection。 这可能是一个安全问题。
解决方案1,3和4没有这个问题,因为它们将用户提供的字符串转换为其他字符串。
与特殊类加载器的reflection
您无法在所有环境中轻松使用此类reflection。 例如,使用OSGi时可能会遇到问题。
解决方案1没有此问题,因为它不使用reflection。
带参数的构造函数
给定的示例仍然很简单,因为它不使用构造函数参数。 使用具有构造函数参数的类似模式是很常见的。 在这种情况下,解决方案2,3和4变得丑陋,请参阅我可以将Class.newInstance()与构造函数参数一起使用吗?
解决方案1只需将Supplier
更改为与构造函数签名匹配的function界面。
使用工厂(方法)创建复杂的水果
解决方案2,3和4要求您通过构造函数实例化水果。 但是,这可能是不合需要的,因为您通常不希望将复杂的初始化逻辑放入构造函数中,而是放入工厂(方法)。
解决方案1没有此问题,因为它允许您将任何创建水果的function放入地图中。
代码复杂性
以下是引入代码复杂性的元素,以及它们出现的解决方案:
- 在1,3和4中创建地图
- 2,3和4中的exception处理
上面已经讨论了exception处理。
映射是代码的一部分,用于将用户提供的String转换为其他内容。 因此,地图解决了上述许多问题,这意味着它有助于实现目的。
请注意,映射也可以由List
或数组替换。 然而,这并没有改变上述任何结论。
码
共同代码
public interface Fruit { public static void printOptional(Optional 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"; } }
抽象工厂(1)
public class AbstractFactory { public static void main(String[] args) { // this needs to be executed only once Map> 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> createMap() { Map> result = new HashMap<>(); result.put("apple", Apple::new); result.put("banana", Banana::new); return result; } private static Optional create( Map> 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 create(String userChoice) { try { return Optional.of((Fruit) Class.forName(userChoice).newInstance()); } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) { return Optional.empty(); } } }
地图reflection(3)
public class ReflectionWithMap { public static void main(String[] args) { // this needs to be executed only once Map 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 createMap() { Map result = new HashMap<>(); result.put("apple", "stackoverflow.fruit.Apple"); result.put("banana", "stackoverflow.fruit.Banana"); return result; } private static Optional create( Map map, String userChoice) { return Optional.ofNullable(map.get(userChoice)) .flatMap(ReflectionWithMap::instantiate); } private static Optional 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> 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> createMap() { Map> result = new HashMap<>(); result.put("apple", Apple.class); result.put("banana", Banana.class); return result; } private static Optional create( Map> map, String userChoice) { return Optional.ofNullable(map.get(userChoice)) .flatMap(ReflectionWithClassMap::instantiate); } private static Optional instantiate(Class extends Fruit> c) { try { return Optional.of(c.newInstance()); } catch (InstantiationException | IllegalAccessException e) { return Optional.empty(); } } }