Есть ли лучший способ, чем использование отражения для создания элементов методом фабрики в иерархии наследования? - PullRequest
1 голос
/ 01 июля 2019

Я построил иерархию наследования, в которой группа конкретных классов наследуется от абстрактного суперкласса A.A имеет обязательный атрибут String a и дополнительный Map b и моделирует элементы спецификации модели xml.И a, и возможные пары ключ-значение в b являются частью jaxp.NamedNodeList.Таким образом, чтобы установить значения для a и b, мне всегда нужно перебирать список и проверять, имеет ли текущий атрибут имя «id» или нет, и соответственно устанавливать значение для a или добавлять значение ключапара в b.Очевидно, что кто-то хочет передать это на фабричный метод или тому подобное.Но реализация статического метода фабрики в абстрактном суперклассе A явно недостаточна, так как при возврате нового экземпляра A мне нужно будет уменьшить значение экземпляра для конкретного элемента при его создании с помощью метода фабрики.Поэтому я нашел решение, использующее рефлексию, но я действительно не уверен, что нет более простого способа решения проблемы, кажущейся настолько распространенной.

Есть ли более тривиальное решение для этого?

Это был мой фабричный шаблон, который привел к 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;
}

1 Ответ

2 голосов
/ 01 июля 2019

Ваш init метод, кажется, требует способа создания экземпляра из данного класса на основе одного значения String.

В этомслучай, вам не нужно отражение.Вместо того, чтобы требовать передать Class для создания экземпляра и инициализации, вы можете реализовать форму «шаблона стратегии», где стратегия является переменной и определяет только то, как создать новый объект, инициализированный ID..

В Java 8 и выше вы можете использовать для этого функциональные интерфейсы и лямбды:

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в конструкторе?Или их можно настроить позже?

...