【问题标题】:Is there a better way than using reflection to instantiate elements by a factory method in an inheritance hierarchy?有没有比使用反射通过继承层次结构中的工厂方法实例化元素更好的方法?
【发布时间】:2019-07-01 13:00:35
【问题描述】:

我构建了一个继承层次结构,其中一堆具体类继承自抽象超类AA 具有强制属性 String a 和可选 Map b 和 xml 模型规范的模型元素。 ab 中可能的键值对都是 jaxp.NamedNodeList 的一部分。因此,要设置ab 的值,我总是需要遍历列表并检查当前属性是否具有名称“id”并分别设置a 的值或添加键值与b 配对。显然,有人想将其外包给工厂方法等。但是,在抽象超类A 中实现静态工厂方法显然是不够的,因为通过返回 A 的新实例,我需要在使用工厂方法创建具体元素时将实例化向下转换为具体元素。所以我想出了一个使用反射的解决方案,但我真的很不安全,因为没有更简单的方法可以解决一个看起来如此普遍的问题。

有没有更简单的解决方案?

这是我的工厂模式,当将 A 向下转换为 B 时,会产生 ClassCastException SubclassB b = (SubclassB) AbstractSuperClassA.createWith(attributes);

public static AbstractSuperClassA createWith(NamedNodeMap attributes) {
    Map<String, String> attributeMap = new HashMap<>();
    String a= null;
    for (int i = 0; i < attributes.getLength(); i++) {
        if (attributes.item(i).getNodeName().equals("id")) {
            a = attributes.item(i).getNodeValue();
        }
        attributeMap.put(attributes.item(i).getNodeName(), attributes.item(i).getNodeValue());
    }
    if (a == null) {
        // throw RuntimeException
    }
    return new AbstractSuperClassA (identifier, attributeMap);
}

这是通用反射实现:

public static <T extends AbstractSuperClassA > T init(NamedNodeMap attributes, Class<T> clazz) {
    Map<String, String> attributeMap = new HashMap<>();
    String a= null;
    for (int i = 0; i < attributes.getLength(); i++) {
        if (attributes.item(i).getNodeName().equals("id")) {
            a = attributes.item(i).getNodeValue();
        }
        attributeMap.put(attributes.item(i).getNodeName(), attributes.item(i).getNodeValue());
    }
    if (a== null) {
        // throw RuntimeException
    }
    try {
        Constructor<T> constructor = clazz.getConstructor(String.class);
        T newElement = constructor.newInstance(a);
        newElement.setAttributes(attributeMap);
        return newElement;
    } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
        log.error(e.getMessage(), e);
    }
    return null;
}

【问题讨论】:

  • 你为什么还要创建你的类的实例?只需提取地图并让客户端创建其值为extractedMap.get("id") 的子类实例。或者创建一个构造函数,只取 Map 并提取 id 本身。
  • 这实际上是一个有效的问题。没想到这个,呵呵。有时程序员只看到复杂的解决方案

标签: java inheritance instantiation


【解决方案1】:

您的init 方法似乎需要一种方法来创建基于单个String 值的given 类的instance

在这种情况下,您不需要反思。不需要传入Class 来实例化和初始化,您可以实现一种“策略模式”的形式,其中 策略 是可变的,并且只定义了如何创建一个新的、ID 初始化的对象。

在 Java 8 及更高版本中,您可以为此使用函数式接口和 Lambda:

private <T extends AbstractSuperClassA > T init(NamedNodeMap attributes, Function<String,T> creator) {
  ...
  T newElement = creator.apply(identifier);
  ...
}

然后根据需要使用它,例如

B someB = init(attrs, B::new);
C someC = init(attrs, id -> {C c = new C(); c.setId(id); return c;});
...

但是,问题是您如何决定应该实例化哪个具体类。在任何情况下,该逻辑都必须在某处进行编码,因此可能有更好的方法来连接逻辑以收集值和初始化新实例。

实例是否需要在构造函数中接收id?还是可以稍后设置?

【讨论】:

  • 相当优雅!只要后置条件断言 id 不为空,它就可以在之后轻松设置。去看看吧!
  • 在这种情况下,您可以让抽象类A 只实现一个接口IdSettable 左右,它声明了setId(String) 方法,也可以在每个子类上调用该方法。然后将您想要的任何子类的 instance 传递给 init(...) 并设置该实例。
  • 最后我使用你的代码结合抽象工厂模式将实例化封装在作为数据结构一部分的单个工厂类中,从而调用myAs.getFactory().createElements(nodeList)。像魅力一样工作
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多