Search code examples
javainheritanceinstantiation

Is there a better way than using reflection to instantiate elements by a factory method in an inheritance hierarchy?


I built an inheritance hierarchy in which a bunch of concrete classes inherit from an abstract super class A. A has a mandatory attribute String a and an optional Map b and models elements of an xml model specification. Both a and the possible key-value pairs in b are part of a a jaxp.NamedNodeList. Thus, to set the values for a and b I always need to iterate through the list and check wether the current attribute has name "id" or not and respectively set the value for a or add the key-value pair to b. Clearly one wants to outsource this to a factory method or such. But, implementing a static factory method in the abstract super class A is clearly not sufficient as by returning a new instance of A, I would need to downcast the instantiation to the concrete element when creating it with the factory method. So I came up with a solution using reflection but I am really insecure that there is no easier way for a problem seeming to be so common.

Is there any more trivial solution for this?

This was my factory pattern, which resulted in a ClassCastException when downcasting A to B as such 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);
}

This is the generic reflection implementation:

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;
}

Solution

  • Your init method seems to require a way to create an instance of a given class based on a single String value.

    In that case, you don't need reflection. Instead of requiring to pass in the Class to instantiate and initialize, you can implement a form of 'strategy pattern', where the strategy is variable and only defines how to create a new, ID-initialized object.

    In Java 8 and above, you can use functional interfaces and Lambdas for that:

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

    and then use it as fit, e.g.

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

    The question is, however, how you decide which concrete class should be instantiated. That logic has to be coded somewhere in any case, so there may be better approaches of connecting the logic to gather the values and that of initializing the new instance.

    Is it required that the instances receive the id in the constructor? Or can they be set up later?