Устранение дублирующего кода 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, но это, похоже, не разрешено.
Любые предложения, которые не полагаются на размышления?
Благодаря,
Дон
Ответы
Ответ 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());
}
}
Ответ 2
Абстрактные перечисления потенциально очень полезны (и в настоящее время не разрешены). Но предложение и прототип существуют, если вы хотите лоббировать кого-то в Sun, чтобы добавить его:
http://freddy33.blogspot.com/2007/11/abstract-enum-ricky-carlson-way.html
Sun RFE:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570766
Ответ 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]. Это намного быстрее и проще.
Ответ 11
Если вы действительно хотите наследование, не забывайте, что вы можете реализовать шаблон enum самостоятельно, например, в плохой старой Java 1.4 дня.
Ответ 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);