Динамическое приведение ссылок в зависимости от фактического типа объекта - PullRequest
0 голосов
/ 23 октября 2018

У меня есть иерархия классов, такая как

ChildA extends Parent
ChildB extends Parent
ChildC extends Parent

, тогда в моем приложении я получаю метод любого из этого потомка по ссылке Родителя.

Проблема в том, что все эти дети имеютте же методы, но их родитель не

Итак, ChildA, ChildB, and ChildC все имеют getSomeValue() геттер, но Parent не имеет.

Теперь мне нужно проанализировать значение из любого изэтот дочерний элемент, но ссылка на родительский элемент не предоставляет мне API, поэтому мне нужно привести родительский тип к определенному типу дочерних элементов.

Ниже приведен фрагмент, представляющий то, что я пытаюсь сделать:

private void processChildren(Parent parent) {
    ChildA childA = null;
    ChildB childB = null;
    ChildC childC = null;

    if (parent instanceof ChildA) {
        childA = parent;
    }

    if (parent instanceof ChildB) {
        childB = parent;
    }

    if (parent instanceof ChildC) {
        childC = parent;
    }

    String someValue;

    if (Objects.nonNull(childA)) {
        someValue = childA.getSomeValue();
    } // and the same checks and extracts for each of childs and for many methods

}

Как вы можете видеть, чтобы извлечь только одно значение, мне нужно создать 3 ссылки, затем проверить их, чтобы привести к конкретному типу, а затем проверить, какой тип фактически был создан для вызова метода.

Вопрос в том, как правильно привести ссылку на конкретную дочернюю ссылку во время выполнения?Я думаю, что можно писать с помощью отражения, хотя я не смог решить его даже с помощью отражения.

Кроме того, даже если это возможно - нормально ли это делать?

К вашему сведению:Я работаю над унаследованным приложением, поэтому не могу изменить ранее написанный код, поэтому я не могу добавить этот API в родительский класс.Кроме того, классы предоставляются из внешнего банка.

Ответы [ 4 ]

0 голосов
/ 23 октября 2018

В качестве возможного решения вы можете создать карту, параметризованную конкретным дочерним классом в качестве ключа и поставщиком в качестве значения.Каждый поставщик отвечает за кастинг и работу с определенными методами.

0 голосов
/ 23 октября 2018

Если вы уверены, что у всех детей есть метод getSomeValue, вы можете назвать их как

parent.getClass().getMethod("getSomeValue").invoke(parent);

Однако это не очень хорошая практика.Дизайн класса нарушает принцип подстановки Лискова здесь .

0 голосов
/ 23 октября 2018

Мой подход заключается в том, чтобы сделать так, как я бы расширил Parent отсутствующим интерфейсом.Для этого я определяю интерфейс, который объявляет недостающие методы:

public interface HasSomeValue {

    String getSomeValue();

}

Поскольку невозможно Parent самому реализовать этот интерфейс, я определяю оболочку для Parent:

private static class ParentWithSomeValue implements HasSomeValue {

    private Parent parent;

    public ParentWithSomeValue(Parent parent) {
        this.parent = parent;
    }

    @Override
    public String getSomeValue() {
        try {
            Method method = parent.getClass().getMethod("getSomeValue");
            return (String) method.invoke(parent);
        } catch (Exception e) {
            return null;
        }
    }
}

Более того ParentWithSomeValue делегирует другим методам, которые Parent уже имеет.Затем ParentWithSomeValue предоставляет полный интерфейс Parent и может его заменить.

getSomeValue реализован с использованием отражения.Если parent имеет вызываемый метод, то вызов делегируется parent с использованием отражений.В противном случае возвращается значение по умолчанию.В моем примере null.Другим вариантом будет генерировать исключение, если parent должен иметь вызываемый метод.

Используя этот подход, вы также можете расширить реализованный интерфейс или добавить дополнительные интерфейсы и реализовать делегирование для каждого метода.

Применяя это к вашему коду:

private void processChildren(Parent parent) {
    if (Objects.nonNull(parent)) {
        ParentWithSomeValue parentWithSomeValue = new ParentWithSomeValue(parent);
        String someValue = parentWithSomeValue.getSomeValue();
        // go on
    } 
}
0 голосов
/ 23 октября 2018

Вопрос в том, как правильно привести ссылку, которая определенно является одним из этих типов, к конкретной ссылке?

Используйте оператор instanceof для проверки точного типа экземпляра родительской ссылки.

    if (parent instanceof ChildA) {
        ChildA childA = (ChildA) parent;
    } else if (parent instanceof ChildB) {
        ChildB childB = (ChildB) parent;
    } else if (parent instanceof ChildC) {
        ChildC childAC = (ChildC) parent;
    }
...