[英]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. 我们需要几个调色板(例如Tango , Gnome等),每个调色板都有一组有限的命名颜色。 Our requirements are: 我们的要求是:
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值用作参数。
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. 列举所有调色板/颜色也是如此。
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. 另外,枚举所有调色板或调色板中的所有颜色已成为“针对每个”循环的问题,并且非常有效。
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
!
I like enum
s so the last solution appeals most to me. 我喜欢enum
所以最后一个解决方案对我最有吸引力。 Generally, I'd use the following rule of thumb: 通常,我会使用以下经验法则:
enum
s especially if you only need one level of recursion. 特别是在只需要一级递归的情况下,请使用enum
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.