Правильный способ (ы) для создания нескольких глобальных констант?

1

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

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

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

Каковы правильные способы определения таких констант? Должны ли они быть жестко закодированы вообще, как сейчас?

Пример:

public class ParameterSet {

    //hundreds of similar declarations..........

    public static ParameterTypeAbstract BREADCRUMB1_BREADCRUMB2_BREADCRUMB3_BREADCRUMB4_452 = new ParameterTypeSet(
            KeywordType.PARAMETER_OF_SOME_TYPE_26659,
            ParameterGroupType.PARAMETER_OF_SOME_OTHER_TYPE_967347,
            ParameterScopeType.ACCOUNT_TYPE_557.ACCOUNT_SUB_TYPE_33791.getStringCode(), ParameterScopeImportantType.getStringCodes(),
            "Some description",
            true, true, true, null, null);

    public static ParameterTypeAbstract BREADCRUMB1_BREADCRUMB2_BREADCRUMB3_BREADCRUMB4_453 = new ParameterTypeSet(
            KeywordType.PARAMETER_OF_SOME_TYPE_90689,
            ParameterGroupType.PARAMETER_OF_SOME_OTHER_TYPE_867335,
            ParameterScopeType.ACCOUNT_TYPE_538.ACCOUNT_SUB_TYPE_48224.getStringCode(), ParameterScopeImportantType.getStringCodes(),
            "Some other description",
            true, true, true, null, null);

    //hundreds of similar declarations..........
}
  • 3
    ваши константы должны быть конечными. иначе ....;)
  • 0
    Никто не меняет их.
Показать ещё 12 комментариев
Теги:
design-patterns
design
anti-patterns

1 ответ

0

Вот некоторые стратегии, которые я использовал в прошлом. Было бы легче ответить, если бы я знал, какую фактическую проблему вы пытаетесь решить и, честно говоря, я не могу действительно сильно понимать BREADCRUMB1_BREADCRUMB2_BREADCRUMB3_BREADCRUMB4_452. Чтобы дать иллюстративный ответ, я приведу следующий пример: предположим, что мы хотим сделать некоторую графику и использовать для этого цветовые палитры. Мы хотим иметь несколько палитр (например, Tango, Gnome и т.д.) С конечным набором именованных цветов. Наши требования:

  • Обеспечьте согласованный интерфейс с вложенной структурой констант.
  • Обеспечьте безопасный статический тип обращения к цвету в палитре.
  • Обеспечьте удобный (и, желательно, эффективный) способ поиска (во время выполнения) палитр и цветов по имени.
  • Обеспечьте удобный (и предпочтительно эффективный) способ перечислить (во время выполнения) все палитры и цвета.
  • Предпочитайте внешние источники конфигурации.

Мы хотим представлять цвета экземплярами неизменяемого класса Color, у которого есть конструктор, который принимает упакованное значение RGB в качестве аргумента.

Вложенные классы с статическими константами

public final class Palettes {

    public static final class Tango {

        public static final Color BUTTER_LIGHT = new Color(0xFCE94F);
        public static final Color BUTTER_MEDIUM = new Color(0xEDD400);
        public static final Color BUTTER_DARK = new Color(0xC4A000);
        public static final Color ORANGE_LIGHT = new Color(0xFCAF3E);
        public static final Color ORANGE_MEDIUM = new Color(0xF57900);
        // Many more Tango colors...

        private Tango() { throw new Error("cannot be instantiated"); }
    }

    public static final class Gnome {

        public static final Color BASIC_3D_HIGHLIGHT = new Color(0xEAE8E3);
        public static final Color BASIC_3D_DARK = new Color(0x807D74);
        public static final Color GREEN_HIGHLIGHT = new Color(0xC5D2C8);
        // Many more Gnome colors...

        private Gnome() { throw new Error("cannot be instantiated"); }
    }

    // Many more color palettes...

    private Palettes() { throw new Error("cannot be instantiated"); }
}

Эти классы тривиальны и могут быть легко сгенерированы как часть процесса сборки. Лично мне нравится хранить такие данные в файлах XML и использовать стили XSLT для генерации кода Java (или любого другого). Я "отключил" конструкторы классов и объявил их final так как класс с только статическими членами не должен быть ни экземпляром, ни подклассом.

Посмотрим, насколько это решение соответствует нашим требованиям.

Если мы хотим ссылаться на цвет в палитре и знать, какой из них мы хотим во время компиляции, мы можем сделать это следующим образом:

Color color1 = Palettes.Tango.ORANGE_LIGHT;

Это дает нам статическую проверку типа; если мы случайно ссылаемся на несуществующий цвет или палитру или пропустим имя, компилятор скажет нам.

Color color2 = Palettes.Gonme.GREEN_HIGHLIGHT;  // compile-time error
Color color3 = Palettes.Gnome.GREEN_HIHGLIGHT;  // compile-time error

Что делать, если мы получим во время выполнения палитру и имя цвета от пользователя и хотим найти ее? Для этого нет удобного способа. Не может быть никаких сомнений в том, что следующее решение является ужасным, даже если код можно легко создать из того же источника данных, что и классы.

public Color selectColor(final String palette, final String color) {
    switch (palette) {
    case "Tango":
        switch (color) {
        case "Butter Light":       return Palettes.Tango.BUTTER_LIGHT;
        case "Butter Medium":      return Palettes.Tango.BUTTER_MEDIUM;
        case "Butter Dark":        return Palettes.Tango.BUTTER_DARK;
        case "Orange Light":       return Palettes.Tango.ORANGE_LIGHT;
        case "Orange Medium":      return Palettes.Tango.ORANGE_MEDIUM;
        // case ...
        default: throw new IllegalArgumentException(String.format(
            "There is no color '%s' in palette '%s'", color, palette));
        }
    case "Gnome":
        switch (color) {
        case "Basic 3D Highlight": return Palettes.Gnome.BASIC_3D_HIGHLIGHT;
        case "Basic 3D Dark":      return Palettes.Gnome.BASIC_3D_DARK;
        case "Green Highlight":    return Palettes.Gnome.GREEN_HIGHLIGHT;
        // case ...
        default: throw new IllegalArgumentException(String.format(
            "There is no color '%s' in palette '%s'", color, palette));
        }
    // case ...
    default:
        throw new IllegalArgumentException(String.format(
            "There is no palette '%s'", palette));
    }
}

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

То же самое относится к перечислению всех палитр/цветов.

Вложенные словари

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

final class GlobalConstants {

    public static final Map<String, Map<String, Color>> COLOR_PALETTES = GlobalConstants.makeColorPalettes();

    private static Map<String, Map<String, Color>> makeColorPalettes() {
        final Map<String, Map<String, Color>> colorPalettes = new HashMap<String, Map<String, Color>>();
        // These could be loaded at class load time from an external
        // configuration.
        {
            final Map<String, Color> palette = new HashMap<String, Color>();
            palette.put("Butter Light",       new Color(0xFCE94F));
            palette.put("Butter Medium",      new Color(0xFCE94F));
            palette.put("Butter Dark",        new Color(0xFCE94F));
            palette.put("Orange Light",       new Color(0xFCE94F));
            palette.put("Orange Medium",      new Color(0xFCE94F));
            // Many more Tango colors...
            colorPalettes.put("Tango", Collections.unmodifiableMap(palette));
        }
        {
            final Map<String, Color> palette = new HashMap<String, Color>();
            palette.put("Basic 3D Highlight", new Color(0xEAE8E3));
            palette.put("Basic 3D Dark",      new Color(0x807D74));
            palette.put("Green Highlight",    new Color(0xC5D2C8));
            // Many more Gnome colors...
            colorPalettes.put("Gnome", Collections.unmodifiableMap(palette));
        }
        // Many more color palettes...
        return Collections.unmodifiableMap(colorPalettes);
    }

    private GlobalConstants() { throw new Error("cannot be instantiated"); }
}

Мой выбор java.util.Map - скорее местозаполнитель, чем предложение. Вероятно, следует определить специализированный класс ColorPalette с более описательным именем, которое предоставляет необходимые методы.

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

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

Color color1 = GlobalConstants.COLOR_PALETTES.get("Tango").get("Orange Light");

Однако теперь это будут ошибки во время выполнения:

Color color2 = GlobalConstants.COLOR_PALETTES.get("Gonme").get("Green Highlight");  // NullPointerException
Color color3 = GlobalConstants.COLOR_PALETTES.get("Gnome").get("Green Hihglight");  // color3 == null, surprise will come later

Поиск палитр и цветов во время выполнения теперь тривиальна, так как мы делаем это эффективно так же, как мы делаем это для констант времени компиляции.

public Color selectColor(final String palette, final String color) {
    final Map<String, Color> thePalette;
    final Color theColor;
    thePalette = GlobalConstants.COLOR_PALETTES.get(palette);
    if (thePalette == null)
        throw new IllegalArgumentException(String.format(
            "There is no palette '%s'", palette));
    theColor = thePalette.get(color);
    if (theColor == null)
        throw new IllegalArgumentException(String.format(
            "There is no color '%s' in palette '%s'", color, palette));
    return theColor;
}

Кроме того, перечисление всех палитр или всех цветов в палитре стало просто вопросом "для каждого" цикла и будет очень эффективным.

Классы Enum

Наконец, мы рассмотрим Java настолько мощные enum.

enum TangoPalette {

    BUTTER_LIGHT(new Color(0xFCE94F)),
    BUTTER_MEDIUM(new Color(0xEDD400)),
    BUTTER_DARK(new Color(0xC4A000)),
    ORANGE_LIGHT(new Color(0xFCAF3E)),
    ORANGE_MEDIUM(new Color(0xF57900));
    // Many more Tango colors...

    private final Color color;

    TangoPalette(final Color color) {
        this.color = color;
    }

    public Color asColor() {
        return this.color;
    }
}

enum GnomePalette {

    BASIC_3D_HIGHLIGHT(new Color(0xEAE8E3)),
    BASIC_3D_DARK(new Color(0x807D74)),
    GREEN_HIGHLIGHT(new Color(0xC5D2C8));
    // Many more Gnome colors...

    private final Color color;

    GnomePalette(final Color color) {
        this.color = color;
    }

    public Color asColor() {
        return this.color;
    }
}

// Many more palettes...

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

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

Вот как мы ссылаемся на константы во время компиляции:

Color color1 = TangoPalette.ORANGE_LIGHT.asColor();
Color color2 = GonmePalette.GREEN_HIGHLIGHT.asColor();  // compile-time error
Color color3 = GnomePalette.GREEN_HIHGLIGHT.asColor();  // compile-time error

Эта прибыль снова возвращается из полной проверки статического типа. .asColor() писать .asColor() не приятно, но только незначительное раздражение, на мой взгляд.

Удобство поиска цветов во время выполнения находится где-то между этими двумя подходами. Нам еще нужно написать собственную логику, чтобы выбрать палитру, но как только мы ее получим, мы можем использовать метод valueOf.

public Color selectColor(final String palette, final String color) {
    final String mangledColor = color.replace(' ', '_').toUpperCase();
    switch (palette) {
    case "Tango":
        return TangoPalette.valueOf(mangledColor).asColor();
    case "Gnome":
        return GnomePalette.valueOf(mangledColor).asColor();
    // case ...
    default:
        throw new IllegalArgumentException(String.format(
            "There is no palette '%s'", palette));
    }
}

Аналогично, перечисление цветов из данной палитры - это просто вопрос использования метода values.

Если бы мы могли сделать дополнительный enum с именами всех палитр!

Вывод

Мне нравится enum поэтому последнее решение больше всего нравится мне. Как правило, я бы использовал следующее эмпирическое правило:

  • Если вам нужна рекурсивная вложенность, используйте первый подход с вложенными классами с общедоступными статическими константами.
  • Если кажется неправдоподобным, что значения будут загружены из внешней базы данных в будущем, рассмотрите возможность использования словарей.
  • Однако скорее избегайте словарей, если большинство значений уже известно во время компиляции для проверки статического типа.
  • Используйте enum особенно если вам нужен только один уровень рекурсии.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню