简体   繁体   English

创建多个全局常量的正确方法?

[英]Right way(s) to create multiple global constants?

I work on a (kind of old) application where there's a maze of huge global enums / classes that declare constant keys / classes that declare constant sets of key/values. 我在一个(有点旧的)应用程序上工作,那里有一个迷宫,它们声明了常量键,而这些全局枚举/类声明了常量键,而类则声明了键/值的常量集。

Many of these values are basically redundant... the same basic constant is declared in different enums under slightly different names, in each one of them it is associated with a different set of values. 这些值中的许多基本上是多余的...在不同的枚举中,以略有不同的名称声明相同的基本常量,在每个值中,它都与一组不同的值相关联。

Often times such a class refers to values in other similar huge classes, to create some hierarchy of constants... as you can see in the example below. 通常,这样的类会引用其他类似的大型类中的值,以创建常量的层次结构……如下面的示例所示。

What are the right ways to define such constants? 定义此类常量的正确方法是什么? Should they be hard coded at all like now? 是否应该像现在这样硬编码?

Example: 例:

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

Here are some strategies I have used in the past. 这是我过去使用的一些策略。 It would have been easier to answer, if I knew what actual problem you are trying to solve and frankly, I can't really make much sense of BREADCRUMB1_BREADCRUMB2_BREADCRUMB3_BREADCRUMB4_452 . 坦率地说,如果我知道您要解决的实际问题,那会更容易回答,我对BREADCRUMB1_BREADCRUMB2_BREADCRUMB3_BREADCRUMB4_452并没有多大意义。 To give an illustrative answer, I will assume the following example: Suppose we want to do some graphics and use color palettes for that. 为了给出说明性的答案,我将假设以下示例:假设我们要绘制一些图形并为此使用调色板。 We want several palettes (eg Tango , Gnome , etc) each with a finite set of named colors. 我们需要几个调色板(例如TangoGnome等),每个调色板都有一组有限的命名颜色。 Our requirements are: 我们的要求是:

  • Provide a consistent interface to the nested structure of constants. 为常量的嵌套结构提供一致的接口。
  • Provide a statically type safe way to refer to a color in a palette. 提供静态类型安全的方式来引用调色板中的颜色。
  • Provide a convenient (and preferably efficient) way to look up (at run-time) palettes and colors by name. 提供一种方便的(最好是高效的)方式来(在运行时)按名称查找调色板和颜色。
  • Provide a convenient (and preferably efficient) way to enumerate (at run-time) all palettes and colors. 提供一种方便的方法(最好是有效的方法)来枚举(在运行时)所有调色板和颜色。
  • Prefer external configuration sources. 首选外部配置源。

We want to represent colors by instances of the immutable Color class that has a constructor that takes a packed RGB value as argument. 我们希望通过具有构造器的不可变Color类实例来表示颜色,该构造器将打包的RGB值用作参数。

Nested Classes with Pubic Static Constants 具有公共静态常量的嵌套类

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

These classes are trivial and can easily be generated as part of the build process. 这些类很简单,可以很容易地在构建过程中生成。 Personally, I like keeping such data in XML files and using XSLT style-sheets to generate Java (or whatever) code from them. 就个人而言,我喜欢将此类数据保存在XML文件中,并使用XSLT样式表从中生成Java(或任何其他形式)代码。 I have “disabled” the constructors of the classes and declared them final since a class with only static members should be neither instantiated nor subclassed. 我已经“禁用”了类的构造函数,并声明了它们的final因为只有静态成员的类既不能实例化也不能子类化。

Let's see how well this solution fits our requirements. 让我们看看该解决方案符合我们要求的程度。

If we want to refer to a color in a palette and know which one we want at compile time, we can do it like this: 如果我们要引用调色板中的一种颜色并在编译时知道我们想要哪种颜色,我们可以这样做:

Color color1 = Palettes.Tango.ORANGE_LIGHT;

This gives us static type checking; 这给了我们静态类型检查; if we accidentally refer to a non-existing color or palette or misspell a name, the compiler will tell us. 如果我们不小心引用了不存在的颜色或调色板或拼写错误的名称,则编译器会告诉我们。

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

What if we get at run-time a palette and color name from a user and want to look it up? 如果我们在运行时从用户那里获得调色板和颜色名称并想要查找该怎么办? There is no convenient way for this. 没有方便的方法。 There can be no doubt that the following solution is a terrible one, even if the code can be easily generated from the same data source as the classes. 毫无疑问,以下解决方案是一个糟糕的解决方案,即使可以轻松地从与类相同的数据源中生成代码。

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

An alternative would be to use reflection, which is probably better but still an indicator of a poor design choice. 一种替代方法是使用反射,这可能会更好,但仍然表明设计选择不佳。

The same goes for enumerating all palettes / colors. 列举所有调色板/颜色也是如此。

Nested Dictionaries 嵌套词典

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

My choice of java.util.Map is more a placeholder than a suggestion. 我对java.util.Map选择更多是占位符而不是建议。 One should probably define a specialized ColorPalette class with a more descriptive name that provides the needed methods. 可能应该定义一个专门的ColorPalette类,并使用更具描述性的名称来提供所需的方法。

Once we have decided to put the constants into dictionaries that are assembled at run-time, we need no longer use code generation to hardcode our constants into Java source. 一旦决定将常量放入运行时汇编的字典中,就不再需要使用代码生成将常量硬编码为Java源代码。 We can still do that if we want (generating a file as the one shown) but we could equally well read the information from a property file, query a database or whatever. 如果需要,我们仍然可以这样做(生成一个如图所示的文件),但是我们同样可以从属性文件中读取信息,查询数据库或进行其他操作。

This comes, of course, at the cost of lost static type checking. 当然,这是以丢失静态类型检查为代价的。 We can still conveniently refer to any color in any palette: 我们仍然可以方便地引用任何调色板中的任何颜色:

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

However, these will now be run-time errors: 但是,这些现在将是运行时错误:

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

Looking up palettes and colors at run-time is now trivial since we do it effectively the same way as we do it for compile-time constants. 现在,在运行时查找调色板和颜色很简单,因为我们有效地执行了与编译时常量相同的方式。

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

In addition, enumerating all palettes or all colors in a palette has become just a matter of a “for each” loop and will be very efficient. 另外,枚举所有调色板或调色板中的所有颜色已成为“针对每个”循环的问题,并且非常有效。

Enum Classes 枚举类

Finally, we'll have a look at Java's ever so powerful enum s. 最后,我们将看一下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...

This is again a candidate for code generation. 这也是代码生成的候选对象。 What is probably most offending is that we have to repeat the logic for associating a Color with an enum in every palette. 可能最令人讨厌的是,我们必须重复将Color每个调色板中的enum关联的逻辑。 It doesn't look that bad in this example but will become worse once we want more data than a simple RBG value, perhaps with additional convenience methods. 在此示例中,看起来并不那么糟糕,但是一旦我们想要的数据比简单的RBG值更多,可能还会变得更糟,也许还有其他便利方法。

Otherwise, this solution seems almost perfect, except for one major drawback: we cannot nest enum s. 否则,此解决方案似乎几乎是完美的,除了一个主要缺点:我们不能嵌套enum (At least, I'm not aware of a way to do this.) This means it is effectively a mix of the above two approaches. (至少,我不知道执行此操作的方法。)这意味着它实际上是上述两种方法的组合。

Here is how we refer to constants at compile time: 这是我们在编译时引用常量的方式:

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

This profits again from full static type checking. 这再次从完全静态类型检查中受益。 Having to write .asColor() is not nice but only a minor annoyance in my opinion. 在我看来,必须编写.asColor()并不好,而只是一个小麻烦。

The convenience of looking up colors at run-time is somewhere in between the above two approaches. 在运行时查找颜色的便利性介于上述两种方法之间。 We still need to write our own logic to select the palette but once we have it, we can use the valueOf method. 我们仍然需要编写自己的逻辑来选择调色板,但是一旦有了调色板,就可以使用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));
    }
}

Likewise, enumerating the colors from a given palette is simply a matter of using the values method. 同样,枚举给定调色板中的颜色仅是使用values方法的问题。

If only we could make an additional enum with the names of all the palettes! 如果只有我们可以用所有调色板的名称做一个附加的enum

Conclusion 结论

I like enum s so the last solution appeals most to me. 我喜欢enum所以最后一个解决方案对我最有吸引力。 Generally, I'd use the following rule of thumb: 通常,我会使用以下经验法则:

  • If you need recursive nesting, use the first approach with nested classes with public static constants. 如果需要递归嵌套,请对具有公共静态常量的嵌套类使用第一种方法。
  • If it seems not implausible that values will be loaded from an external database in the future, consider using dictionaries. 如果将来似乎无法从外部数据库加载值似乎不合理,请考虑使用字典。
  • However, rather stay away from dictionaries if most values are already known at compile-time for the sake of static type checking. 但是,如果为了静态类型检查而在编译时已经知道大多数值,则不要使用字典。
  • Use enum s especially if you only need one level of recursion. 特别是在只需要一级递归的情况下,请使用enum

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 用Java发布全局常量的更好方法是什么? - What is the better way of publishing global constants in Java? 当存在大量常量时,定义常量的正确方法是什么? - What is the right way to define constants when there is a huge number of constants? Java:导入多个常量的简洁方法 - Java: Succinct way to import multiple constants 如何以正确的方式为多个对象创建一个对象? - How to create one object for multiple uses the right way? 这是为我的servlet拥有“全局”参数的正确方法吗? - Is this the right way to have “global” parameters for my servlets? 如何在Java程序中创建常量的最佳方法? - How can be the best way to create constants in Java program? 在现有代码中为常量创建静态导入的简单方法? - A simple way to create static imports for constants in existing code? 在Java中声明项目常量的正确方法是什么? - What's the proper way of declaring project constants in Java? 在spring xml配置中使用应用程序常量的最佳方法是什么? - what's the best way to use application constants in spring xml configuration? 使用权限在Realm Object Server上创建新用户的域的正确方法 - Right way to create new user's Realms on Realm Object Server with permissions
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM