【问题标题】:Adding subcategories to a java Enum将子类别添加到 java Enum
【发布时间】:2016-01-05 17:15:50
【问题描述】:

假设我有一个简单的 Java 枚举:

public Enum itemType
{
    FRUITS("fru"),
    VEGETABLES("veg"),
    LIQUOURS("liq"),
    SODAS("sod");

    private String dbCode;

    public ItemType(String dbCode){
        this.dbCode = dbCode;
    }

    public String getDbCode(){
        return this.dbCode;
    }
}

我现在想为这个枚举引入一个“类别”,例如区分液体物品和固体物品。我在枚举类中找到了两种方法,见下文。但是,两者都遭受相同的反模式:如果类别的数量或项目的数量曾经增加/减少(想象一下 100 个项目类型和 10 个类别!),我有很多更新要做。我可以使用哪些模式来设计此枚举,使其尽可能干净且可重用?

第一种方法:向枚举添加额外的属性

public Enum itemType
{
    FRUITS("fru",false),
    VEGETABLES("veg",false),
    LIQUOURS("liq",true),
    SODAS("sod",true);

    private String dbCode;
    private boolean liquid;

    public ItemType(String dbCode, boolean liquid){
        this.dbCode = dbCode;
        this.liquid = liquid;
    }

    public String getDbCode(){
        return this.dbCode;
    }
    public boolean isLiquid(){
        return this.liquid;
    }
}

第二种方法:使用静态方法询问子类别

public Enum itemType
{
    FRUITS("fru"),
    VEGETABLES("veg"),
    LIQUOURS("liq"),
    SODAS("sod");

    private String dbCode;

    public ItemType(String dbCode){
        this.dbCode = dbCode;
    }

    public String getDbCode(){
        return this.dbCode;
    }

    public static boolean isLiquid(ItemType type){
        switch(t){
            case SODA:
            case LIQOURS: return true;
            default: return false;
        }
}

【问题讨论】:

  • 如果你有很多类别,为什么不添加一个类实例而不是真/假、字符串、整数等?该类可以包含该特定枚举的所有属性。
  • 假设每个枚举类型都有一个具有许多属性的 pojo 类。这些属性最终将是布尔值、字符串等。您认为我如何轻松设置所有这些属性(每种枚举类型将具有不同的属性值组合......)?

标签: java design-patterns enums


【解决方案1】:

为此使用EnumSet 怎么样?

public enum ItemType
{
    FRUITS("fru"),
    VEGETABLES("veg"),
    LIQUOURS("liq"),
    SODAS("sod");

    public static final EnumSet<ItemType> LIQUIDS = EnumSet.of(LIQUOURS, SODAS);

    // ...
}

然后您可以使用ItemType.LIQUIDS.contains(someItemType) 来检查someItemType 是否为“液体”。

【讨论】:

  • 太棒了,我不知道 EnumSets 存在!
【解决方案2】:

我会这样做:

enum Category {
    LIQUID, SOLID;
}

enum ItemType {
    FRUITS("fru", SOLID),
    VEGETABLES("veg", SOLID),
    LIQUOURS("liq", LIQUID),
    SODAS("sod", LIQUID);

    private String dbCode;
    private Category category;
    public ItemType(String dbCode, Category category){
        this.dbCode = dbCode;
        this.category = category;
    }

    /* getters / setters */
}

例如,您可以添加新产品和类别(例如BUTANE("but", GAS)),而无需修改现有代码(方法 2 中会发生这种情况)。

另一方面,如果类别和项目的数量很长且不断变化,我会考虑使用 SQL 数据库。

【讨论】:

  • 我认为你说得对,在它变得不可维护之前,应该允许枚举增长多大,并且应该考虑其他数据结构?另一件事,当我说“添加新类别”时,我指的不是一种新的类别(如 GAS),而是一个全新的类别,如“颜色”(蓝色、红色、绿色、黄色)或包含酒精(真的/错误的)。如果 itemtypes 的数量很大,我觉得你的方法仍然需要大量的手动工作来包含一个新的类别。
【解决方案3】:

由于您正在建模的东西没有可以以算法方式编码的逻辑(即没有算法可以确定"sod" 是流动的而"veg" 不是),因此无法枚举所有相关的以一种或另一种方式成对的(项目,类别)。

实现它的三种方法:

  • 枚举项目一侧的类别 - 这是您的代码在两种情况下所做的,或者
  • 枚举类别一侧的项目 - 这将构建一个enum 类别,并为每个类别附加完整的项目列表,或者
  • 独立枚举项目+类别对 - 这种方法在将项目/类别映射存储在数据库或配置文件中时可能很有用。

我建议采用第三种方法,因为它是最“对称”的一种。为具有类别代码的类别制作一个表格,并添加一个包含所有类别对及其对应项目的“交叉表”(或交叉文件)。启动时读取交叉表/文件,并设置双方的依赖关系。

public Enum ItemType {
    FRUITS("fru")
,   VEGETABLES("veg")
,   LIQUOURS("liq")
,   SODAS("sod");
    public void addCategory(ItemCategory category) ...;
    public EnumSet<ItemCategory> getItemCategories() ...;
}
public Enum ItemCategory {
    LIQUIDS("liq")
,   SNACKS("snk")
,   FAST("fst");
    public void addItem(ItemType type) ...;
    public EnumSet<ItemType> getItemTypes() ...;
}

跨文件或跨表可能如下所示:

liq liq
sod liq
fru snk
fru fst
sod fst

您通过枚举对来处理它,并在对的item 端调用addCategory,在对的category 端调用addItem

【讨论】:

  • 非常感谢您的比较。在我的特定用例中,我不希望 DB 或(文本)文件来备份我的数据,而 EnumSet 构造使我能够以紧凑且简单的方式枚举类别,而无需处理任何内容。所以目前我更喜欢这种选择。但是,我一定会记住这种方法。还是您认为我标记为正确的答案有缺点?
  • @user1884155 为什么,标记的答案是方法#2(即在类别一侧列出项目)。
【解决方案4】:

这是三个很好的答案,但我认为我可以将所有三个结合到一个很好的包中:

public enum ItemType {
    FRUITS("fru",PERISHABLE),
    VEGETABLES("veg",PERISHABLE),
    LIQUOURS("liq",LIQUIDS),
    SODAS("sod",LIQUIDS),
    FRESH_SQUEEZED_ORANGE_JUICE("orgj",LIQUIDS,PERISHABLE);

    private final String dbCode;
    private final EnumSet<ItemCategory> categories;
    private static final Map<ItemCategory,Set<ItemType>> INDEX_BY_CATEGORY = new EnumMap<>(ItemCategory.class);

    ItemType(String dbcode,ItemCategory... categories) {
      this.dbCode = dbcode;
      this.categories = EnumSet.copyOf(Arrays.asList(categories));
      //for (ItemCategory c:categories) {
      //  // Illegal Reference to Static Field!
      //  INDEX_BY_CATEGORY.put(c, this);
      //}
    }

    static {
      for (ItemCategory c:ItemCategory.values()) {
        INDEX_BY_CATEGORY.put(c, EnumSet.noneOf(ItemType.class));
      }
      for (ItemType t:values()) {
        for (ItemCategory c:t.categories) {
          INDEX_BY_CATEGORY.get(c).add(t);
        }
      }
    }

    public boolean is(ItemCategory c) {
      return INDEX_BY_CATEGORY.get(c).contains(this);
    }

    public Set<ItemType> getAll(ItemCategory c) {
      return EnumSet.copyOf(INDEX_BY_CATEGORY.get(c));
    }

    public String getDbCode() {
      return dbCode;
    }
}

现在,

  • 我们可以很容易地询问其他子类别而无需为其编写代码:boolean isVegetableLiquid = VEGETABLES.is(LIQUIDS);
  • 我们可以轻松地为一个项目不仅分配一个类别,还可以分配多个类别,正如您在 FRESH_SQUEEZED_ORANGE_JUICE 看到的那样。
  • 我们使用EnumSetEnumMap 来提高性能,包括他们的方法,例如contains
  • 我们绝对会尽量减少添加额外项目所需的代码量。这可以通过数据库或配置来进一步最小化。但是,在这种情况下,我们也必须避免使用 Enum

【讨论】:

    猜你喜欢
    • 2015-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-16
    • 2021-08-10
    • 1970-01-01
    相关资源
    最近更新 更多