我应该如何根据用户选择选择应该实例化哪个具体实现? [英] How should I select which concrete implementation should be instantiated based on the user choice?

查看:158
本文介绍了我应该如何根据用户选择选择应该实例化哪个具体实现?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have an interface Fruit with two implementations Apple and Banana. I want to create a Fruit instance. The choice whether the concrete implementation should be an Apple or a Banana should be made by the user. I did not yet design the user interface, so there is no restriction how this choice is made by the user.

我知道有以下选项:


  1. 抽象工厂模式的使用

  2. 使用反射来创建给定类名的实例

  3. 使用反射从给定的类对象创建实例

有什么优点和缺点这些选项?

What are the pros and cons of these options?

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

Please note that while there are several similar questions that discuss the one or the other approach, I did not find a single comparison.

以下是相关问题列表:

  • Why is Class.newInstance() "evil"?
  • Can I use Class.newInstance() with constructor arguments?
  • How to make an ArrayList of Classes?
  • Does Class.newInstance() follow the "Abstract factory" design pattern?

推荐答案

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

tl;dr I suggest to use the abstract factory pattern.

长答案:

To比较方法,我在下面附上了四种可能的解决方案。以下是摘要:

To compare the approaches, I attached four possible solutions below. Here is a summary:


  1. 使用抽象工厂模式

  2. 使用直接选择的String由用户按名称实例化一个类

  3. 获取一个由用户直接选择的字符串,并将其转换为另一个字符串以按名称实例化一个类

  4. 获取一个由用户直接选择的String,并将其转换为Class对象以实例化该类

  1. uses the abstract factory pattern
  2. uses a String which is directly chosen by the user to instantiate a class by name
  3. takes a String which is directly chosen by the user and translates it to another String to instantiate a class by name
  4. takes a String which is directly chosen by the user and translates it to a Class object to instantiate the class



比较



使用 Class :: forName



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

Comparison

Using Class::forName

First of all, the reflection solutions 2 and 3 identify the class object with a String that provides the class name. Doing this is bad, because it breaks automatic refactoring tools: When you rename the class, the String will not be changed. Also, there will be no compiler error. The error will only become visible at run-time.

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

Please note that this does not depend on the quality of the refactoring tool: In solution 2, the String which provides the class name might be constructed in the most obscure way that you can think of. It might even be entered by the user or read from a file. There is no way a refactoring tool can entirely solve this problem.

解决方案1和4没有这些问题,因为它们直接链接到类。

Solution 1 and 4 do not have these problems, since they directly link to the classes.

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

Since solution 2 directly uses the String given by the user for reflection to identify a class by name, the GUI is coupled to the class names that you use in your code. This is bad, since this requires you to change the GUI when you rename your classes. Renaming classes should always be as easy as possible to enable easy refactoring.

解决方案1,3和4没有这个问题,因为它们翻译了由GUI的其他内容。

Solution 1, 3 and 4 does not have this problem, since they translate the String which is used by the GUI to something else.

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

Solution 2, 3 and 4 have to deal with exceptions when using the reflection methods forName and newInstance. Solution 2 even has to use the exceptions for flow control, since it does not have any other way to check whether the input is valid. Using exceptions for flow control is generally considered bad practice.

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

Solution 1 does not have this problem, since it does not use reflection.

解决方案2直接使用用户提供的字符串进行反射。这可能是一个安全问题。

Solution 2 directly uses the String provided by the user for reflection. This can be a security issue.

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

Solution 1, 3 and 4 does not have this problem, since they translate the String which is provided by the user to something else.

在所有环境中都不能轻易使用这种类型的反射。例如,使用OSGi时可能会遇到问题。

You cannot easily use this type of reflection in all environments. For example you will probably run into problem when using OSGi.

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

Solution 1 does not have this problem, since it does not use reflection.

给定的示例仍然很简单,因为它不使用构造函数参数。使用具有构造函数参数的类似模式是很常见的。在这种情况下,解决方案2,3和4变得丑陋,请参阅我可以使用带构造函数参数的Class.newInstance()?

The given example is still simple, because it does not use constructor parameters. It is quite common to use a similar pattern with constructor parameters. Solution 2, 3 and 4 become ugly in this case, see Can I use Class.newInstance() with constructor arguments?

解决方案1只需要将 Supplier 更改为一个与构造函数签名匹配的函数接口。

Solution 1 only has to change the Supplier to a functional interface which matches the constructor signatures.

解决方案2,3和4要求您通过构造函数实例化水果。但是,这可能是不合需要的,因为您通常不希望将复杂的初始化逻辑放入构造函数中,而是放入工厂(方法)。

Solution 2, 3 and 4 require that you instantiate the fruit via the constructor. However, this might be undesirable, since you generally don't want to put complex initialization logic into constructors, but into a factory (method).

解决方案1没有这个问题,因为它允许你将任何创建水果的函数放入地图中。

Solution 1 does not have this problem, since it allows you to put any function which creates a fruit into the map.

以下是引入代码复杂性的元素,以及它们出现的解决方案:

Here are the elements which introduce code complexity, together with the solutions where they appear:


  • 在1,3和4中创建地图

  • 2,3和4中的异常处理

上面已经讨论过异常处理。

The exception handling was already discussed above.

地图是代码的一部分,用于将用户提供的字符串转换为其他字符串。因此,地图解决了上述许多问题,这意味着它有用。

The map is the part of the code which translates the String provided by the user to something else. Thus, the map is what solved many of the problems described above which means it serves a purpose.

请注意,地图也可以替换为列表或数组。但是,这不会改变上述任何结论。

Note that the map can also be replaced by a List or an array. However this does not change any of the conclusions stated above.

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)



Abstract Factory (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)



Reflection (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)



Reflection with Map (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 )



Reflection with Class Map (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();
        }
    }
}

这篇关于我应该如何根据用户选择选择应该实例化哪个具体实现?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆