Как избежать чрезмерного дублирования кода при использовании перечислений в Java - PullRequest
3 голосов
/ 15 июля 2011

Я занимаюсь рефакторингом какого-то устаревшего кода и столкнулся с проблемой, которая, я уверен, имеет элегантное решение, - но я не могу до нее добраться.

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

enum XmlElementTag {value1, value2, value3}

У каждого из них также есть метод:

private XmlElementTag getTag(String s){
    XmlElementTag ret = null;
    try {
        ret = XmlElementTag.valueOf(s);
    } catch (Exception e) {
        Log.e(this, s+" is not supported tag");
    }
    return ret;
}

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

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

Итак, у меня есть это в каждом классе:

private XmlElementTag implements GenericTag {value1, value2, value3}; 

И это в суперклассе BaseType:

public interface GenericTag {}

protected GenericTag getTag(String tagName){
    XmlElementTag tag = null;
    try {
        tag = XmlElementTag.valueOf(tagName);
    } catch (Exception e) {
        Log.e(this, tagName+" is not supported tag");
    }
    return tag;
}

Но опять же, это не работает, поскольку суперкласс BaseType не знает, что такое XmlElementTag; Java не допускает абстрактные переменные класса; и создание этого элемента в BaseType не будет работать, так как код getTag всегда будет ссылаться на это перечисление, а не на класс в классе, который расширяет BaseType.

Кто-нибудь может указать мне правильное направление?

Ответы [ 5 ]

3 голосов
/ 15 июля 2011

Полагаю, вы могли бы написать статический вспомогательный метод, который бы делал то, что делает getTag. Для этого потребуется использовать отражение под капотом и, скорее всего, потребуется передать объект Class перечисления в качестве параметра.

Но ИМО, ты не должен. Метод getTag() является своего рода ошибочным. Это превращает то, что фактически плохо вводит в null. Это неправильно с двух точек зрения:

  • В большинстве случаев "ты дал мне плохие вещи" не следует рассматривать как "ты дал мне ничего".
  • Если вы не будете особенно осторожны, эти null значения вернутся, чтобы укусить вас как NullPointerException s.

Таким образом, код вашего приложения должен либо поймать IllegalArgumentException, возникающий при неправильном преобразовании, либо иметь дело с ним, либо он должен позволить исключению всплыть вверх, где о нем можно сообщить (например) ошибка синтаксического анализа входного потока.

(я не думаю, что enum может расширяться или расширяться, поэтому я не думаю, что ваши enum могут наследовать универсальную версию этого класса.)

3 голосов
/ 15 июля 2011

Возможно, вам удастся объединить элементы XmlElementTag в один enum и установить EnumSet по отношению к каждому производному типу. Вот пример здесь .

Добавление: в этой схеме getTag() станет единым методом комбинированного enum. Каждый производный класс будет вызывать getTag(), используя Set, который он считает допустимым. У метода может быть такая подпись:

public static XmlElementTag getTag(Set valid, String s) { ... }
1 голос
/ 15 июля 2011

К сожалению, перечисления Java не имеют хорошего метакласса (Class - зло).Однако все, что вам действительно нужно, это список (массив) значений перечисления.

Поскольку это частный метод, вы также можете использовать композицию.

import static java.util.Objects.requireNonNull;

/* pp */ class EnumFinder<E extends Enum<E>> {
    private final E[] tags;
    protected BaseType(E[] tags) {
        this.tags = requireNonNull(tags);
    }

    public E getTag(String name) {
        requireNonNull(name);
        for (E tag : tags) {
            if (name.equals(tag.name())) {
                return tag;
            }
        }
        Log.e(this, name+" is not supported tag"); // (sic)
        return null; // (sic)
    }
    ...
}

public class DerivedType {
    private static final EnumFinder<XmlElementType> finder = // note, shared
        new EnumFinder<>(XmlElementType.values());
    ...
        finder.getTag(name)
    ...
}

(Создайте Map<String,E>, если действительно хотите. Не требуется для перечислений разумного размера.)

Если вы действительно хотите использовать наследование, то это почти то же самое.(К сожалению, поскольку мы используем массив, если вы не добавите больше шаблона в свой код, этот код создаст ненужный дополнительный массив для каждого экземпляра - вероятно, не является существенной проблемой, но может быть.):

/* pp */ abstract class BaseType<E extends Enum<E>> {
    private final E[] tags;
    protected BaseType(E[] tags) {
        this.tags = requireNonNull(tags);
    }

    public E getTag(String name) {
        requireNonNull(name);
        for (E tag : tags) {
            if (name.equals(tag.name())) {
                return tag;
            }
        }
        Log.e(this, name+" is not supported tag"); // (sic)
        return null; // (sic)
    }
    ...
}
public class DerivedType extends BaseType<XmlElementType> {
    public DerivedType() {
        super(XmlElementType.values());
    }
    ...
        this.getTag(name)
    ...
}
1 голос
/ 15 июля 2011

Вы можете использовать генерики для этого:

База

public abstract class Base {
protected static <T extends Enum<T>> T getTag(Class<T> enumType, String s) {
    T ret = null;
    try {
        ret = Enum.valueOf(enumType, s);
    } catch (Exception e) {
        System.err.println(s + " is not supported tag");
    }
    return ret;
}

protected abstract <T extends Enum<T>> T getTag(String s);
}

У ваших многочисленных классов короче getTag() (и вся логика в Base)

public class ClassA extends Base {
enum XmlElementTag {
    UL, LI
}

@Override
protected XmlElementTag getTag(String s) {
    return Base.getTag(XmlElementTag.class, s);
}
}

(то же самое для ClassB)

0 голосов
/ 15 июля 2011

Я думаю, что вы хотели достичь чего-то подобного (поправьте меня, если я ошибаюсь):

interface GenericTag {

    public GenericTag fromString(String str) throws IllegalArgumentException;
}


class BaseType {

    protected GenericTag getTag(String tagName) {
        GenericTag tag = null;
        try {
            tag = tag.fromString(tagName); 
        } catch (Exception e) {
            Log.e(this, tagName+" tag is not supported");
        }
        return tag;
    }

}

class ConcreteTypeA extends BaseType {

    enum XmlElementTag implements GenericTag {
        TAG1, TAG2;

        public GenericTag fromString(String str) throws IllegalArgumentException {
            return XmlElementTag.valueOf(str);
        }
    }
}

Однако это никогда не сработает. Вам потребуется метод fromString для возврата экземпляра соответствующего класса (в данном случае enum), реализующего GenericTag, но метод fromString должен выполняться тем конкретным классом, которого у вас еще нет, так что вы получите исключение NullPointerException. Это своего рода проблема курицы и яйца! :)

...