有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

在Java中将类A(和所有子类)映射到类B的实例的字典

在Java中实现strategy patterny时,有一个常见的问题是将输入数据类型映射到特定算法的实例

例如,我们可以创建一张地图:

map.put(Apple.class, new AppleHandler());
map.put(Orange.class, new OrangeHandler());

并在运行时为给定数据类型解析正确的处理程序:

Apple a = new Apple();
map.get(a.getClass()).handle(a);

问题:在Guava/ApacheCommons等此类映射库中是否有任何Map<Class<A>, B>实现,可以处理多态类型

例如,对于上述映射,其工作原理如下(假设GreenApple扩展了Apple):

  map.get(Apple.class) == map.get(GreenApple.class);

我知道我可以编写跨越层次结构的实现,但我想问的是OTS解决方案


共 (2) 个答案

  1. # 1 楼答案

    我已经通过注释解决了类似的问题,如

    @Documented @Inherited
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Handler {
        Class<? extends AbstractHandler> value();
    }
    

    通过这种方式,我得到了一个处理程序类,而不是处理程序本身,但在我的例子中,这很好。您可以使用Class#newInstance或依赖项注入来获取处理程序实例


    另一种可能是像这样的查找循环

    for (Class<?>  cl = givenClass; cl != null; cl = cl.getSuperclass()) {
        Class<? extends AbstractHandler> result = map.get(cl);
        if (result != null) {
            return result;
        }
    }
    ... handle "handler not found"
    

    这适用于超类,但不适用于接口。可以对它们进行扩展,但之后可能会遇到the diamond problem

    我做过一次,钻石问题对我来说没有问题,因为我的经纪人被严格地排在了优先顺序上。我的最终优化解决方案是使用ConcurrentMap<Class<?>, <Class<AbstractHandler>>>,每次遇到新类时我都会更新它

  2. # 2 楼答案

    最简单的解决方案是简单地为具有多个类的处理程序添加多个映射项:

    map.put(Apple.class, new AppleHandler());
    map.put(GreenApple.class, map.get(Apple.class));
    map.put(Orange.class, new OrangeHandler());
    

    这种方法的一些注意事项包括

    1. 如果你有很多类,那么你的地图可能会有点大
    2. 如果外部源可以定义类型为A的新类并在运行时提供它们,那么您也需要某种方法来注册它们(例如,在初始化时扫描类路径,提供某种类型的注册API供外部代码使用,或者如果请求的键不在映射中,则添加对遍历层次结构的支持)

    但是,如果您可以控制源代码,则可以在编译时在类本身中进行此“映射”:

    interface A {
        B getHandler();
    }
    
    interface B {
    }
    
    class Apple implements A {
        public B getHandler() {
            return AppleHandler.INSTANCE;
        }
    }
    
    class GreenApple extends Apple {
    }
    
    class Orange implements A {
        public B getHandler() {
            return OrangeHandler.INSTANCE;
        }
    }
    

    现在,只需调用类AgetHandler()方法,就可以获得类A的任何实例的处理程序。由于getHandler()是合同的一部分,定义A新类的外部源也必须实现它

    如果无法更改类型A的源或不想更改(到separate concerns,等等),则可以引入新的类型包装A,以使用decorator pattern定义映射