устранение дублирующегося кода перечисления

StackOverflow https://stackoverflow.com/questions/77213

  •  09-06-2019
  •  | 
  •  

Вопрос

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

/**
 * Interface for an enumeration, each element of which can be uniquely identified by it's 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);  
}

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

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

Есть какие-нибудь предложения, которые не основаны на размышлениях?Спасибо, Дон

Это было полезно?

Решение

Вы могли бы разложить дублированный код на 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());
    } 
}

Другие советы

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

http://freddy33.blogspot.com/2007/11/abstract-enum-ricky-carlson-way.html

Вс РФЭ:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570766

Чтобы привести в порядок код Дейва:

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));
    } 
}

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

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

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

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

HTH,
Грег

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

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

Интерфейс сводится к следующему:

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 :)

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

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 метода, но эти методы являются всего лишь делегаторами.

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

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

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

Как насчет статического универсального метода?Вы могли бы повторно использовать его из методов getByCode() вашего перечисления или просто использовать его напрямую.Я всегда использую целочисленные идентификаторы для своих перечислений, поэтому мой метод GetById () выполняет только это:возвращает значения()[id].Это намного быстрее и проще.

Если вы действительно хотите получить наследство, не забывайте, что вы можете реализуйте шаблон перечисления самостоятельно, как в старые добрые времена Java 1.4.

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

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

Другим решением было бы ничего не помещать в само перечисление, а просто предоставить двунаправленное перечисление карт <-> Код для каждого перечисления.Вы могли бы, например,использование Неизменяемый образ из коллекций 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();

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

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

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);
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top