我应该如何 select 根据用户的选择实例化哪个具体实现?

How should I select which concrete implementation should be instantiated based on the user choice?

我有一个接口 Fruit,有两个实现 AppleBanana。我想创建一个 Fruit 实例。具体实现是 Apple 还是 Banana 应该由用户选择。我还没有设计用户界面,所以没有限制用户如何做出这个选择。

我知道有以下几种选择:

  1. 抽象工厂模式的使用
  2. 使用反射从给定的 class 名称创建实例
  3. 使用反射从给定的 class 对象创建实例

这些选项的优缺点是什么?


请注意,虽然有几个类似的问题讨论了一种或另一种方法,但我没有找到一个比较。

这里是相关问题的列表:

tl;dr 我建议使用抽象工厂模式

长答案:

为了比较这些方法,我在下面附上了四种可能的解决方案。这是一个摘要:

  1. 使用抽象工厂模式
  2. 使用用户直接选择的字符串实例化一个class名字
  3. 获取用户直接选择的字符串并将其转换为另一个字符串以按名称
  4. 实例化class
  5. 获取用户直接选择的字符串并将其转换为 Class 对象以实例化 class

比较

使用Class::forName

首先,反射方案2和3用提供class名称的String来识别class对象。这样做很糟糕,因为它破坏了自动重构工具:重命名 class 时,字符串不会更改。此外,不会有编译器错误。该错误仅在 运行 时可见。

请注意,这不取决于重构工具的质量:在解决方案 2 中,提供 class 名称的字符串可能以您能想到的最晦涩的方式构造。它甚至可能由用户输入或从文件中读取。重构工具无法完全解决这个问题。

解决方案 1 和 4 没有这些问题,因为它们直接 link 到 classes.

GUI 与 class 名称的耦合

由于解决方案 2 直接使用用户提供的字符串进行反射以按名称识别 class,因此 GUI 与您在代码中使用的 class 名称相关联。这很糟糕,因为这要求您在重命名 classes 时更改 GUI。重命名 classes 应始终尽可能简单,以便于重构。

解决方案 1、3 和 4 没有这个问题,因为它们将 GUI 使用的字符串转换为其他内容。

流量控制异常

解决方案2、3、4在使用反射方法forNamenewInstance时必须处理异常。解决方案 2 甚至必须使用异常来进行流量控制,因为它没有任何其他方法来检查输入是否有效。使用异常进行流量控制通常被认为是不好的做法。

解决方案1没有这个问题,因为它没有使用反射。

反射的安全问题

方案二直接使用用户提供的String进行反射。这可能是一个安全问题。

解决方案 1、3 和 4 没有这个问题,因为它们将用户提供的字符串转换为其他内容。

使用特殊 class 加载程序进行反射

您无法在所有环境中轻松使用这种类型的反射。例如,您可能 运行 在使用 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";
    }
}

抽象工厂 (1)

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("Whosebug.fruit.Apple"));
        // prints "The fruit is yellow."
        Fruit.printOptional(create("Whosebug.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", "Whosebug.fruit.Apple");
        result.put("banana", "Whosebug.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();
        }
    }
}

反射与 Class 地图 (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();
        }
    }
}