Reputation: 3736
Suppose I have a simple Java Enum:
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;
}
}
I would now like to introduce a "category" to this enum, for example to make the distinction between liquid items and solid items. I found two ways of doing this within the enum class, see below. However, both suffer from the same anti-pattern: if the amount of categories or amount of items ever increases/decreases (imagine 100 item types with 10 categories!), I've got a lot of updating to do. What patterns can I use to design this enum as cleanly and re-usable as possible?
First approach: Add additional properties to the enum
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;
}
}
Second approach: Use static methods to ask about subcategories
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;
}
}
Upvotes: 1
Views: 1869
Reputation: 9405
These were three excellent answers, but I think I can combine all three in one nice package:
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;
}
}
Now,
boolean isVegetableLiquid = VEGETABLES.is(LIQUIDS);
FRESH_SQUEEZED_ORANGE_JUICE
.EnumSet
and EnumMap
for performance, including their methods like contains
.Enum
as well.Upvotes: 1
Reputation: 726499
Since you are modeling something that has no logic that can be encoded in an algorithmic way (i.e. there's no algorithm that would figure out that "sod"
is liquid and "veg"
is not) there is no way around enumerating all related pairs of (item, category) in one way or the other.
There are three approaches to implementing it:
enum
of categories, and attach a full list of items to each of them, orI would recommend taking the third approach as it is the most "symmetric" one. Make a table for categories with category codes, and add a "cross-table" (or a cross-file) that has all pairs of categories and their corresponding items. Read the cross table/file at startup, and set up the dependencies on both sides.
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() ...;
}
Cross-file or cross-table may look like this:
liq liq
sod liq
fru snk
fru fst
sod fst
You process it by enumerating pairs, and calling addCategory
on the pair's item
side, and calling addItem
on the pair's category
side.
Upvotes: 2
Reputation: 1781
I would do something like:
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 */
}
That would allow, for example, that you can add new products and categories (e.g. BUTANE("but", GAS)
) without having to modify the existing code (as would happen in Approach 2).
On the other hand, if the number of categories and items is long and changing, I would consider to use a SQL database.
Upvotes: 3
Reputation:
How about using an EnumSet
for that?
public enum ItemType
{
FRUITS("fru"),
VEGETABLES("veg"),
LIQUOURS("liq"),
SODAS("sod");
public static final EnumSet<ItemType> LIQUIDS = EnumSet.of(LIQUOURS, SODAS);
// ...
}
Then you can use ItemType.LIQUIDS.contains(someItemType)
to check if someItemType
is a "liquid".
Upvotes: 5