Подтвердить что ты не робот

Устранение дублирующего кода Enum

У меня есть большое количество Enums, которые реализуют этот интерфейс:

/**
 * Interface for an enumeration, each element of which can be uniquely identified by it code
 */
public interface CodableEnum {

    /**
     * Get the element with a particular code
     * @param code
     * @return
     */
    public CodableEnum getByCode(String code);

    /**
     * Get the code that identifies an element of the enum
     * @return
     */
    public String getCode();
}

Типичный пример:

public enum IMType implements CodableEnum {

    MSN_MESSENGER("msn_messenger"),
    GOOGLE_TALK("google_talk"),
    SKYPE("skype"),
    YAHOO_MESSENGER("yahoo_messenger");

    private final String code;

    IMType (String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }   

    public IMType getByCode(String code) {
        for (IMType e : IMType.values()) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
    }
}

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

public abstract class DefaultCodableEnum implements CodableEnum {

    private final String code;

    DefaultCodableEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return this.code;
    }   

    public abstract CodableEnum getByCode(String code);  
}

Но это оказывается бесполезным, потому что:

  • Перечисление не может расширить класс
  • Элементы перечисления (SKYPE, GOOGLE_TALK и т.д.) не могут расширять класс
  • Я не могу предоставить реализацию getByCode() по умолчанию, потому что DefaultCodableEnum сам по себе не является Enum. Я попытался изменить DefaultCodableEnum для расширения java.lang.Enum, но это, похоже, не разрешено.

Любые предложения, которые не полагаются на размышления? Благодаря, Дон

4b9b3361

Ответ 1

Вы можете указать дублированный код в класс CodeableEnumHelper:

public class CodeableEnumHelper {
    public static CodeableEnum getByCode(String code, CodeableEnum[] values) {
        for (CodeableEnum e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

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

public enum IMType implements CodeableEnum {
    ...
    public IMType getByCode(String code) {
        return (IMType)CodeableEnumHelper.getByCode(code, this.values());
    } 
}

Ответ 3

Чтобы убрать код дэйва:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> E getByCode(
        String code, E[] values
    ) {
        for (E e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

public enum IMType implements CodableEnum {
    ...
    public IMType getByCode(String code) {
        return CodeableEnumHelper.getByCode(code, values());
    } 
}

Или более эффективно:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> Map<String,E> mapByCode(
        E[] values
    ) {
        Map<String,E> map = new HashMap<String,E>();
        for (E e : values) {
            map.put(e.getCode().toLowerCase(Locale.ROOT), value) {
        }
        return map;
    }
}

public enum IMType implements CodableEnum {
    ...
    private static final Map<String,IMType> byCode =
        CodeableEnumHelper.mapByCode(values());
    public IMType getByCode(String code) {
        return byCode.get(code.toLowerCase(Locale.ROOT));
    } 
}

Ответ 4

У меня была аналогичная проблема с компонентом локализации, который я написал. Мой компонент предназначен для доступа к локализованным сообщениям с константами перечисления, которые индексируются в пакет ресурсов, а не с жесткой проблемой.

Я обнаружил, что я копировал и вставлял один и тот же код шаблона "шаблон" повсюду. Мое решение избежать дублирования - это генератор кода, который принимает файл конфигурации XML с именами констант enum и конструкторами args. Результатом является исходный код Java с "дублированными" поведением.

Теперь я сохраняю файлы конфигурации и генератор, а не весь дублированный код. Всюду, где у меня был бы исходный код enum, теперь есть файл конфигурации XML. Мои скрипты сборки обнаруживают устаревшие сгенерированные файлы и вызывают генератор кода для создания кода перечисления.

Вы можете увидеть этот компонент здесь. Шаблон, который я копировал и вставлял, был заменен на таблицу стилей XSLT. Генератор кода запускает преобразование таблиц стилей. входной файл довольно краток по сравнению с сгенерированным исходным кодом enum.

НТН,
Грег

Ответ 5

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

Ответ 6

Создайте класс безопасности типа, который будет загружать перечисления по коду:

Интерфейс сводится к:

public interface CodeableEnum {
    String getCode();
}

Класс утилиты:

import java.lang.reflect.InvocationTargetException;


public class CodeableEnumUtils {
    @SuppressWarnings("unchecked")
    public static <T extends CodeableEnum>  T getByCode(String code, Class<T> enumClass) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        T[] allValues = (T[]) enumClass.getMethod("values", new Class[0]).invoke(null, new Object[0]);
        for (T value : allValues) {
            if (value.getCode().equals(code)) {
                return value;
            }
        }
        return null;
}

}

Тест-сценарий, демонстрирующий использование:

import junit.framework.TestCase;


public class CodeableEnumUtilsTest extends TestCase {
    public void testWorks() throws Exception {
    assertEquals(A.ONE, CodeableEnumUtils.getByCode("one", A.class));
      assertEquals(null, CodeableEnumUtils.getByCode("blah", A.class));
    }

enum A implements CodeableEnum {
    ONE("one"), TWO("two"), THREE("three");

    private String code;

    private A(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }   
}
}

Теперь вы только дублируете метод getCode(), и метод getByCode() находится в одном месте. Возможно, было бы неплохо обернуть все исключения в одном исключении RuntimeException:)

Ответ 7

Здесь у меня есть другое решение:

interface EnumTypeIF {
String getValue();

EnumTypeIF fromValue(final String theValue);

EnumTypeIF[] getValues();

class FromValue {
  private FromValue() {
  }

  public static EnumTypeIF valueOf(final String theValue, EnumTypeIF theEnumClass) {

    for (EnumTypeIF c : theEnumClass.getValues()) {
      if (c.getValue().equals(theValue)) {
        return c;
      }
    }
    throw new IllegalArgumentException(theValue);
  }
}

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

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

Ответ 8

Кажется, что вы на самом деле реализуете информацию типа времени выполнения. Java предоставляет это как функцию языка.

Я предлагаю вам посмотреть RTTI или отражение.

Ответ 9

Я не думаю, что это возможно. Однако вы можете использовать метод enum valueOf (String name), если вы собираетесь использовать имя значения перечисления в качестве кода.

Ответ 10

Как насчет статического общего метода? Вы можете повторно использовать его из методов enum getByCode() или просто использовать его напрямую. Я всегда являюсь пользователем integer id для моих перечислений, поэтому мой метод getById() только делает это: return values ​​() [id]. Это намного быстрее и проще.

Ответ 12

Примерно так же, как я понял, нужно создать шаблон в IntelliJ, который "реализует" общий код (используя enum valueOf (String name)). Не идеально, но работает достаточно хорошо.

Ответ 13

В вашем конкретном случае методы getCode()/getByCode (String code) кажутся очень закрытыми (эвфемистически) для поведения методов toString()/valueOf (String value), предоставляемых всеми перечислениями. Почему вы не хотите их использовать?

Ответ 14

Другим решением было бы не вносить что-либо в сам перечисление и просто предоставить двунаправленную карту Enum ↔ Code для каждого перечисления. Вы можете, например, используйте ImmutableBiMap из Коллекций Google для этого.

Таким образом, нет дублирующего кода.

Пример:

public enum MYENUM{
  VAL1,VAL2,VAL3;
}

/** Map MYENUM to its ID */
public static final ImmutableBiMap<MYENUM, Integer> MYENUM_TO_ID = 
new ImmutableBiMap.Builder<MYENUM, Integer>().
put(MYENUM.VAL1, 1).
put(MYENUM.VAL2, 2).
put(MYENUM.VAL3, 3).
build();

Ответ 15

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

Вы создаете интерфейс, который реализует ваш enum:

public interface EnumWithId {

    public int getId();

}

Затем в классе-помощнике вы просто создаете метод, подобный этому:

public <T extends EnumWithId> T getById(Class<T> enumClass, int id) {
    T[] values = enumClass.getEnumConstants();
    if (values != null) {
        for (T enumConst : values) {
            if (enumConst.getId() == id) {
                return enumConst;
            }
        }
    }

    return null;
}

Затем этот метод можно использовать следующим образом:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);