Как обобщить перечисление Java со статическими членами? - PullRequest
1 голос
/ 10 марта 2010

Я занимаюсь рефакторингом части нашего унаследованного приложения, которое обрабатывает экспорт и импорт таблиц БД из / в листы Excel. У нас есть подкласс Formatter для каждой таблицы, чтобы предоставить определение этой таблицы: сколько у нее столбцов и каково имя, формат и валидатор каждого столбца. Получатели, которые предоставляют эти данные, затем вызываются с помощью Шаблонного метода, который экспортирует / импортирует таблицу. Я извлек данные столбца в перечисление, что значительно упростило код. Форматер теперь выглядит так (некоторые детали опущены для краткости):

public class DamageChargeFormatter extends BaseFormatter {
    public static final int NUM_COLUMNS = 7;

    public enum Column {
        VEHICLE_GROUP(0, "Vehicle Group", /* more params */),
        NAME_OF_PART(1, "Name of Part", /* more params */),
        //...
        LOSS_OF_USE(6, "Loss of Use", /* more params */);

        private static final Map<Integer, Column> intToColumn = new HashMap<Integer, Column>();

        static {
            for (Column type : values()) {
                intToColumn.put(type.getIndex(), type);
            }
        }

        public static TableColumn valueOf(int index) {
            return intToColumn.get(index);
        }

        private int index;
        private String name;

        Column(int index, String name, /* more params */) {
            this.index = index;
            this.name = name;
            //...
        }

        public int getIndex() { return index; }

        public String getName() { return name; }

        // more members and getters...
    }

    protected String getSheetName() {
        return "Damage Charges";
    }

    public String getColumnName(int columnNumber) {
        TableColumn column = Column.valueOf(columnNumber);

        if (column != null) {
            return column.getName();
        }
        return null;
    }

    // more getters...

    protected int getNumColumns() {
        return NUM_COLUMNS;
    }

    protected boolean isVariableColumnCount() {
        return false;
    }
}

Теперь у меня есть около дюжины таких классов, каждый из которых содержит один и тот же код, за исключением того, что NUM_COLUMNS и значения enum Column различны. Есть ли способ как-то это обобщить? Основным препятствием для этого является статический метод Column.valueOf() и статическая константа NUM_COLUMNS. Еще одна проблема, связанная с последним, заключается в том, что он действительно относится к абстракции на один уровень выше, то есть к таблице, а не к отдельному столбцу - было бы неплохо как-то включить это в общее решение.

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

public class GenericFormatter<E extends TableColumn> extends BaseFormatter {
    private Method valueOfMethod;

    public GenericFormatter(Class<E> columnClass) {
        try {
            valueOfMethod = columnClass.getDeclaredMethod("valueOf", Integer.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public String getColumnName(int columnNumber) {
        try {
            @SuppressWarnings("unchecked")
            E elem = (E) valueOfMethod.invoke(columnNumber);

            if (elem != null) {
                return elem.getName();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    //...
}

Обратите внимание, что этот код является чисто экспериментальным, еще не проверенным ...

Есть ли более приятный, чистый, безопасный способ?

1 Ответ

1 голос
/ 10 марта 2010

Может быть, что-то вроде этого:

public class TableMetadata<E extends Enum & TableColumn> {
    private Map<Integer, TableColumn> columns = new HashMap<Integer, TableColumn>();

    public TableMetadata(Class<E> c) {
        for (E e: c.getEnumConstants()) {
            columns.put(e.getIndex(), e);
        }
    }

    public String getColumnName(int index) {
        return columns.get(index).getName();
    }
}

public class GenericFormatter<E extends TableColumn> extends BaseFormatter {  
    private TableMetadata<E> m;  

    public GenericFormatter(TableMetadata<E> m) {  
         this.m = m;
    }  

    public String getColumnName(int columnNumber) {  
        return m.getColumnName(index);
    }  

    //...  
}

РЕДАКТИРОВАНИЕ: Enum добавлено к параметру типа для большей безопасности во время компиляции

...