Как убрать зависимость от значений перечисления Java? - PullRequest
5 голосов
/ 08 апреля 2011

[ Запомните пробел: Я знаю, что лучшим решением будет полное избавление от перечисления, но на сегодня это не вариант, как упомянуто в комментариях, но это запланировано для (далеко) будущее.]

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

Строковые константы

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

Класс Wrapper

Другое решение - использование класса-оболочки с таким жеинтерфейс как перечисление.Перечисление становится классом оболочки, а значения перечисления становятся константами внутри этой оболочки.Мне пришлось написать некоторый код десериализации для обеспечения идентичности объекта (как это делают перечисления), но я не знаю, является ли это правильным решением.Что делать, если используются разные загрузчики классов?Класс-оболочка реализует интерфейс Java, который заменит перечисление в бэкэнде.Но выполнится ли код десериализации в бэкэнде?

Пример для класса-оболочки:

public class Locomotion implements Serializable {
    private static final long serialVersionUID = -6359307469030924650L;

    public static final List<Locomotion> list = new ArrayList<Locomotion>();

    public static final Locomotion CAR = createValue(4654L);
    public static final Locomotion CYCLE = createValue(34235656L);
    public static final Locomotion FEET = createValue(87687L);

    public static final Locomotion createValue(long type) {
        Locomotion enumValue = new Locomotion(type);
        list.add(enumValue);
        return enumValue;
    }

    private final long ppId;

    private Locomotion(long type) {
        this.ppId = type;
    }

    private Object readResolve() throws ObjectStreamException {
        for (Locomotion enumValue : list) {
            if (this.equals(enumValue)) {
                return enumValue;
            }
        }
        throw new InvalidObjectException("Unknown enum value '" + ppId + "'");
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (int) (ppId ^ (ppId >>> 32));
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof Locomotion)) {
            return false;
        }
        Locomotion other = (Locomotion) obj;
        if (ppId != other.ppId) {
            return false;
        }
        return true;
    }
}

У вас уже была такая же проблема?Как ты решил это?

Ответы [ 4 ]

2 голосов
/ 09 апреля 2011

Хорошо, дай мне посмотреть, понимаю ли я.Вы сказали, что

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

Когда вы говорите" значения ", я предполагаю, что вы ссылаетесь на числовое значение, которое вы передаете в конструктор перечисления, а не на сами константы перечисления.

Следовательно, это означает, что фронтенд и бэкэнд будут иметь две разные версии класса enum, но константы enum в них будут одинаковыми.

Я только предполагаю, что связьчерез RMI (но это не совсем ясно в вашем посте).

Теперь сериализация / десериализация перечислений работает иначе, чем с другими объектами.Согласно Спецификации Сериализации Java, когда enum сериализуется, сериализуется только его имя.А когда он десериализован, он создается с использованием метода Enum.valueOf (name).

Итак, ваше первоначальное предложение-оболочка не будет работать, поскольку сервер из-за предусмотренной сериализации Enums никогда не узнает фактическоезначение перечислений в клиенте.

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

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

См. эту статью о динамической загрузке кода с использованием свойства base code в RMI http://download.oracle.com/javase/6/docs/technotes/guides/rmi/codebase.html

Другое возможное решение состоит в том, что вы можете прекратить использование Java Enum и использовать класс Javaс конечными константами, как мы делали в старые времена перед перечислениями, и таким образом вы можете быть уверены, что его значения будут правильно сериализованы при отправке на сервер.

Примерно так

public class Fruit implements Serializable{

    private static final long serialVersionUID = 1L;
    public final Fruit ORANGE = new Fruit("orange");
    public final Fruit LEMON = new Fruit("lemon");

    private String name;

    private Fruit(String name){
        this.name = name;
    }
}

Таким образом, вы можете полностью контролировать то, что происходит после десериализации, и ваш шаблон оболочки может работать таким образом.

Этот тип конструкции не может полностью заменить перечисление, например, он не может бытьиспользуется в выражениях switch.Но если это проблема, вы можете использовать этот объект в качестве параметра, отправляемого на сервер, и позволить серверу пересобрать из него перечисление с его версией класса перечисления.

Следовательно, ваше перечислениеможет иметь два новых метода, один для создания экземпляров Java из самого перечисления:

public static Fruit toFruit(FruitEnum enum);
public FruitEnum valueOf(Fruit fruit);

И вы можете использовать их для преобразования версий параметра для сервера туда и обратно.

1 голос
/ 08 апреля 2011

Для передачи данных между веб-интерфейсом и внутренним интерфейсом необходимо использовать одинаковые версии класса из-за возможной сериализации во время параметров сортировки.И снова им нужно знать точно такие же перечисления или любые другие классы, которые вы пытаетесь использовать.Переключение перечислений на что-то другое тоже не сработает.Вы должны установить для идентификатора известного класса оба.

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

Так что, если вы поместите свой идентификатор базы данных в объект-оболочку, сервер сможет получить объекты из базы данных.Но все же - им обоим нужна одна и та же версия класса-оболочки в их путях классов.

1 голос
/ 08 апреля 2011

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

public enum Giant {Fee, Fi, Fo, Fum};

public void client() {
    Giant giant = Giant.Fee;
    server(giant);
}

public void server(Enum e) {
    String valueForDB = e.name();
        //or perhaps
    String valueForDB = e.toString();
}
0 голосов
/ 08 апреля 2011

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

Я почти всегда нахожу, чтоесли я экстернализую информацию, которая была в перечислениях, то мне придется также экстернализировать несколько других частей, но после всего этого я получу МНОГО кода.

Каждый раз, когда вы на самом деле используетезначения перечисления вы почти наверняка пишете дублирующий код.Я имею в виду, что если у вас есть перечисления, такие как «HEARTS», «DIAMONDS» ...

ЕДИНСТВЕННЫЙ способ их использования в вашем коде - это что-то вроде оператора switch:

switch(card.suit)
case Suit.HEARTS:
    load_graphic(Suit.HEARTS);
    // or better yet:
    Suit.HEARTS.loadGraphic();
    break;
case Suit.SPADES:
    Suit.SPADES.loadGraphic();

...

Теперь, это явно глупо, но я сделал глупое ограничение, чтобы сказать, что вы ИСПОЛЬЗУЛИ значения в коде.Я утверждаю, что если вы не ИСПОЛЬЗУЕТЕ значения, вам не нужно перечислять - Давайте не будем использовать значения в коде и посмотрим:

card.suit.loadGraphic();

Вау, все прошло.Но внезапно весь смысл использования enum исчезает - вместо этого вы избавляетесь от предварительной загрузки всего класса фабрики «Suit» с 4 экземплярами из текстового файла со строками, такими как «Heart.png» и «Spade.png».

Почти каждый раз, когда я использую перечисления, я заканчиваю тем, что делаю их так:

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

...