有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

向java枚举添加子类别的设计模式

假设我有一个简单的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;
    }
}

现在,我想在这个枚举中引入一个“类别”,例如区分液体项目和固体项目。我在enum类中找到了两种方法,见下文。但是,两者都有相同的反模式:如果类别或项目数量不断增加/减少(想象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;
        }
}

共 (4) 个答案

  1. # 1 楼答案

    我会这样做:

    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数据库。

  2. # 2 楼答案

    由于您正在对一些没有逻辑的东西进行建模,而这些逻辑不能以算法的方式进行编码(即,没有算法可以计算出"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来处理它

  3. # 3 楼答案

    ^{}来做这个怎么样

    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是否为“液体”

  4. # 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