Лучший способ создать поведение расширяемого Enum в Java - PullRequest
0 голосов
/ 02 октября 2011

Я хочу создать нечто, похожее на расширяемый Enum (понимание расширения Enums невозможно в Java 6).

Вот что я пытаюсь сделать:
У меня есть много классов "Model"и каждый из этих классов имеет набор полей, которые должны быть связаны с ним.Эти Поля используются для индексации в Картах, которые содержат представления данных.

Мне нужно иметь доступ к Полям из экземпляра класса ИЛИ obj следующим образом:

MyModel.Fields.SOME_FIELD #=> has string value of "diff-from-field-name"

или

myModel.Fields.SOME_FIELD #=> has string value of "diff-from-field-name"

Мне также нужно иметь возможность получить список ВСЕХ полей для Модели

MyModel.Fields.getKeys() #=> List<String> of all the string values ("diff-from-field name")

При определении класса «Поля» для каждой Модели я хотел бы иметь возможностьчтобы сохранить определение в том же файле, что и модель.

public class MyModel {
    public static final Fields extends BaseFields { 
        public static final String SOME_FIELD = "diff-from-field-name";
        public static final String FOO = "bar";
    }

    public Fields Fields = new Fields();

    // Implement MyModel logic
}

Я также хочу, чтобы OtherModel расширял MyModel и мог наследовать поля из MyModel.Fields, а затем добавлять свои собственные поля сверху, если он есть..

public class OtherModel extends MyModel {
   public static final class Fields extends MyModel.Fields { 
        public static final String CAT = "feline";
        ....

Что позволило бы

OtherModel.Fields.CAT #=> feline
OtherModel.Fields.SOME_FIELD #=> diff-from-field-name
OtherModel.Fields.FOO #=> bar
OtherModel.Fields.getKeys() #=> 3 ["feline", "diff-from-field-name", "bar"]

Я пытаюсь сделать определение "полей" в моделях как можно более чистым и простым, насколько это возможно для различных разработчиков.построение этих "Модельных" объектов.

Спасибо

Ответы [ 5 ]

2 голосов
/ 02 октября 2011

Мне нужно иметь доступ к полям из экземпляра класса ИЛИ obj следующим образом:

  MyModel.Fields.SOME_FIELD #=> has string value of "diff-from-field-name"

Это невозможно в Java, если вы не используете настоящий enumили SOME_FIELD является реальным полем.В любом случае «enum» не является расширяемым.

Лучшее, что вы можете сделать в Java 6, это смоделировать перечисление как отображение имен String в значения int.Это расширяемо, но отображение имен в значения влечет за собой затраты времени выполнения ... и вероятность того, что ваш код будет использовать имя, которое не является членом перечисления.


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

0 голосов
/ 08 июля 2012

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

ActiveField: Java-класс, который будет расширяться всеми остальными классами «Fields» (которые будут внутренними классами в моих классах Model). У этого есть нестатический метод "getKeys ()", который просматривает класс "this's" и извлекает из него список всех полей. Затем он проверяет несколько вещей, таких как модификаторы, тип поля и регистр, чтобы убедиться, что он смотрит только на поля, соответствующие моему соглашению: все «ключи поля» должны быть «public static final» типа String, а имя поля должно быть все ВЕРХНИЙ.

public class ActiveField {
private final String key;

protected ActiveField() {
    this.key = null;
}

public ActiveField(String key) {
    System.out.println(key);
    if (key == null) {
        this.key = "key:unknown";
    } else {
        this.key = key;
    }
}

public String toString() {
    return this.key;
}

@SuppressWarnings("unchecked")
public List<String> getKeys() {
    ArrayList<String> keys = new ArrayList<String>();
    ArrayList<String> names = new ArrayList<String>();

    Class cls;
    try {
        cls = Class.forName(this.getClass().getName());
    } catch (ClassNotFoundException e) {
        return keys;
    }

    Field fieldList[] = cls.getFields();

    for (Field fld : fieldList) {
        int mod = fld.getModifiers();

        // Only look at public static final fields
        if(!Modifier.isPublic(mod) || !Modifier.isStatic(mod) || !Modifier.isFinal(mod)) {
            continue;
        }

        // Only look at String fields
        if(!String.class.equals(fld.getType())) {
            continue;
        }

        // Only look at upper case fields
        if(!fld.getName().toUpperCase().equals(fld.getName())) {
            continue;
        }

        // Get the value of the field
        String value = null;
        try {
            value = StringUtils.stripToNull((String) fld.get(this));
        } catch (IllegalArgumentException e) {
            continue;
        } catch (IllegalAccessException e) {
            continue;
        }

        // Do not add duplicate or null keys, or previously added named fields
        if(value == null || names.contains(fld.getName()) || keys.contains(value)) {
            continue;
        }

        // Success! Add key to key list
        keys.add(value);
        // Add field named to process field names list
        names.add(fld.getName());

    }

    return keys;
}

public int size() {
    return getKeys().size();
} 
}

Затем в моих классах "Модель" (которые являются модными обертками вокруг Карты, которые можно проиндексировать с помощью полей Fields)

открытый класс ActiveResource { / ** * Базовые поля для моделирования ActiveResource objs - Все классы, которые наследуются от * ActiveResource должен иметь эти поля / значения (если не переопределено) * / открытый статический класс Fields extends ActiveField { public static final String CREATED_AT = "узел: создан"; public static final String LAST_MODIFIED_AT = "node: lastModified"; }

public static final Fields Fields = new Fields();

    ... other model specific stuff ...

}

Затем я могу создать класс Foo, который расширяет мой класс ActiveResource

public class Foo extends ActiveResource {

public static class Fields extends ActiveResource.Fields {
    public static final String FILE_REFERENCE = "fileReference";
    public static final String TYPE = "type";
}

public static final Fields Fields = new Fields();

    ... other Foo specific stuff ...

Теперь я могу сделать следующее:

ActiveResource ar = new ActiveResource().
Foo foo = new Foo();

ar.Fields.size() #=> 2
foo.Fields.size() #=> 4

ar.Fields.getKeys() #=> ["fileReference", "type", "node:created", "node:lastModified"]
foo.Fields.getKeys() #=> ["node:created", "node:lastModified"]


ar.Fields.CREATED_AT #=> "node:created"
foo.Fields.CREATED_AT #=> "node:created"
foo.Fields.TYPE #=> "type"
etc.

Я также могу получить доступ к полям как к статическим полям из моих объектов Model

Foo.Fields.size(); Foo.Fields.getKeys(); Foo.Fields.CREATED_AT; Foo.Fields.FILE_REFERENCE;

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

0 голосов
/ 02 октября 2011

Curses - По какой-то причине мой очень длинный ответ с предложенным мною решением не был опубликован.

Я просто дам краткий обзор, и если кому-то понадобится больше подробностей, я могу опубликовать его заново, когда у меня будет больше времени / терпения.

Я создал класс Java (называемый ActiveField), из которого всевнутренние поля наследуют.Каждый из классов внутренних полей имеет определенную серию полей:

public static class Fields extends ActiveField {  
     public static final String KEY = "key_value";  
}

В классе ActiveRecord у меня есть нестатический метод getKeys(), который использует отражение для просмотра всех полей в this, перебирает, получает их значения и возвращает их в виде списка.

Кажется, он работает довольно хорошо - дайте мне знать, если вы заинтересованы в более полных примерах кода.

0 голосов
/ 02 октября 2011

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

0 голосов
/ 02 октября 2011

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

Если у вас есть Fields статический внутренний класс где-то вкласс модели, подобный этому:

public class Model {

  public static class Fields {

    public static final String CAT = "cat";
    protected static final List<String> KEYS = new ArrayList<String>();

    static {
      KEYS.add(CAT);
    }

    protected Fields() {}

    public static List<String> getKeys() {
      return Collections.unmodifiableList(KEYS);
    }
  }
}

, и вы расширяете этот класс следующим образом:

public class ExtendedModel extends Model {

  public static class ExtendedFields extend Model.Fields {

    public static final String DOG = "dog";

    static {
      KEYS.add(DOG);
    }

    protected ExtendedFields() {}
  }
}

тогда это просто неправильно.Если вы позвоните Model.Fields.getKeys(), вы получите то, что ожидаете: [cat], но если вы позвоните ExtendedModel.ExtendedFields.getKeys(), вы получите то же самое: [cat], нет dog.Причина: getKeys() является статическим членом Model.Fields, вызов ExtendedModel.ExtendedFields.getKeys() является неправильным , потому что вы действительно вызываете Model.Fields.getKeys().

Таким образом, вы либо работаете с методами экземпляра, либо создаетестатический getKeys() метод во всех ваших подклассах Fields, что так неправильно, я даже не могу описать.


Может быть, вы можете создать Field интерфейс, который ваши клиенты могут реализовать и подключить к немуВаша модель (ы).

public interface Field {
  String value();
}

public class Model {

  public static Field CAT = new Field() { 
    @Override public String value() {
      return "cat";
    }
  };

  protected final List<Field> fields = new ArrayList();

  public Model() {
    fields.add(CAT);
  }

  public List<Field> fields() {
    return Collections.unmodifiableList(fields);
  }      
}

public class ExtendedModel extends Model {

  public static Field DOG= new Field() { 
    @Override public String value() {
      return "dog";
    }
  };

  public ExtendedModel() {
    fields.add(DOG);
  } 
}
...