Jak mogę wyeliminować zduplikowany Kod Enum?

Mam dużą liczbę Enum, które implementują ten interfejs:

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

Typowym przykładem jest:

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

Jak można sobie wyobrazić te metody są praktycznie identyczne we wszystkich implementacjach CodableEnum. Chciałbym wyeliminować to powielanie, ale szczerze mówiąc Nie wiem jak. Próbowałem użyć klasy takiej jak poniżej:

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

Ale okazuje się to dość bezużyteczne, ponieważ:

  1. enum nie może rozszerzyć klasy
  2. Elementy enum (SKYPE, GOOGLE_TALK, itp.) nie można rozszerzyć klasy
  3. nie mogę podać domyślnej implementacji getByCode (), ponieważ DefaultCodableEnum samo w sobie nie jest Enum. Próbowałem zmienić DefaultCodableEnum, aby rozszerzyć Javę.lang.Enum, ale to chyba nie jest dozwolone.

Jakieś sugestie, które nie opierają się na refleksji? Dzięki., Don

Author: Noumenon, 2008-09-16

15 answers

Możesz dodać zduplikowany kod do klasy 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;
    }
}

Każda klasa CodeableEnum musiałaby zaimplementować metodę getByCode, ale rzeczywista implementacja metody została scentralizowana przynajmniej w jednym miejscu.

public enum IMType implements CodeableEnum {
    ...
    public IMType getByCode(String code) {
        return (IMType)CodeableEnumHelper.getByCode(code, this.values());
    } 
}
 13
Author: dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-30 02:51:53

Liczby abstrakcyjne są potencjalnie bardzo przydatne (i obecnie nie są dozwolone). Ale propozycja i prototyp istnieje, jeśli chcesz lobbować kogoś w Sun, aby go dodać:

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

 7
Author: Alex Miller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 21:43:23

Uporządkować kod dave ' a:

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

Lub bardziej efektywnie:

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));
    } 
}
 5
Author: Tom Hawtin - tackline,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-11 01:06:15

Miałem podobny problem z komponentem lokalizacyjnym, który napisałem. Mój komponent jest przeznaczony do dostępu do zlokalizowanych wiadomości ze stałymi enum, które indeksują się w pakiecie zasobów, nie jest to trudny problem.

Odkryłem, że kopiuję i wklejam ten sam "szablon" kod enum wszędzie. Moim rozwiązaniem, aby uniknąć powielania jest generator kodu, który akceptuje plik konfiguracyjny XML z nazwami stałych enum i konstruktorem args. Wyjściem jest kod źródłowy Javy z zachowania "zduplikowane".

Teraz zachowuję pliki konfiguracyjne i generator, a nie cały zduplikowany kod. Wszędzie, gdzie miałbym kod źródłowy enum, jest teraz plik konfiguracyjny XML. Moje Skrypty kompilacji wykrywają nieaktualne wygenerowane pliki i wywołują generator kodu, aby utworzyć kod enum.

Możesz zobaczyć ten komponent tutaj . Szablon, który kopiowałem i wklejałem jest uwzględniany w arkuszu stylów XSLT. Kod generator uruchamia transformację arkusza stylów. Plik wejściowy jest dość zwięzły w porównaniu z wygenerowanym kodem źródłowym enum.

HTH,
Greg

 2
Author: Greg Mattes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-17 16:02:59

Niestety, nie wydaje mi się, aby można było to zrobić. Najlepiej będzie, jeśli zrezygnujesz w emums i użyjesz konwencjonalnych rozszerzeń klasy i statycznych członków. W przeciwnym razie przywyknij do powielania tego kodu. Przepraszam.

 1
Author: Daniel Spiewak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 21:24:58

Utwórz klasę narzędzi bezpiecznych dla typu, która załaduje enums kodem:

Interfejs sprowadza się do:

public interface CodeableEnum {
    String getCode();
}

Klasa użytkowa to:

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

}

Przypadek testowy demonstrujący użycie:

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

Teraz powielasz tylko metodę getCode (), a metoda getByCode () znajduje się w jednym miejscu. Może być też miło zawinąć wszystkie wyjątki w jednym RuntimeException:)

 1
Author: triggerNZ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-17 00:02:55

Tutaj mam inne rozwiązanie:

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

Sztuczka polega na tym, że Klasa wewnętrzna może być używana do przechowywania "metod globalnych".

Dla mnie zadziałało całkiem nieźle. OK, trzeba zaimplementować 3 metody, ale te metody, są tylko delegatorami.
 1
Author: bueyuekt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-11-26 21:09:20

Wygląda na to, że faktycznie implementujesz informacje typu run time. Java udostępnia to jako funkcję językową.

Proponuję sprawdzić RTTI lub reflection.

 0
Author: Dave Hillier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 21:28:28

To chyba niemożliwe. Możesz jednak użyć metody valueOf(String name) enum, jeśli chcesz użyć nazwy wartości enum jako kodu.

 0
Author: Jorn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 21:33:20

A może statyczna metoda generyczna? Możesz użyć go ponownie z poziomu metod getByCode() lub po prostu użyć go bezpośrednio. Ja zawsze user integer ID dla moich enum, więc moja metoda getById () ma tylko do do this: return values () [id]. Jest o wiele szybszy i prostszy.

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 21:37:06

Jeśli naprawdę chcesz dziedziczyć, nie zapominaj, że możesz zaimplementować wzór enum samodzielnie, jak w starej, złej Javie 1.4 days.

 0
Author: Mwanji Ezana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 21:53:48

Mniej więcej tak blisko, jak doszedłem do tego, czego chcesz, było stworzenie szablonu w IntelliJ, który "zaimplementowałby" kod generyczny(używając wartości enum (String name)). Nie idealne, ale działa całkiem dobrze.

 0
Author: Javamann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-16 22:39:10

W twoim konkretnym przypadku, metody getCode() / getByCode(String code) wydają się bardzo zamknięte (eufemistycznie mówiąc) na zachowanie metod toString() / valueOf(String value) dostarczanych przez wszystkie wyliczenia. Dlaczego nie chcesz ich użyć?

 0
Author: Nicolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-17 11:00:54

Innym rozwiązaniem byłoby nie umieszczanie niczego w samym enum, a po prostu dostarczenie dwukierunkowego kodu mapy Enum dla każdego enum. Możesz np. użyć ImmutableBiMap z kolekcji Google.

W ten sposób nie ma duplikatu kodu w ogóle.

Przykład:

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();
 0
Author: sleske,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-18 17:37:33

Moim zdaniem byłby to najprostszy sposób, bez refleksji i bez dodawania dodatkowego opakowania do twojego enum.

Tworzysz interfejs, który implementuje Twój enum:

public interface EnumWithId {

    public int getId();

}

Następnie w klasie pomocniczej tworzysz metodę taką jak Ta:

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

Metoda ta może być użyta w następujący sposób:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);
 0
Author: Marius Burz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-19 15:42:13