I have a class Product. Each product can have any amount of Categories:
public class Product
{
private List<Category> categories
...
}
Each Category has its own unique set of subcategories. For example:
Category 'Red' has subcategories 'Maroon', 'Orange', 'Bordeaux'
Category 'Blue' has subcategories 'Light', 'Dark', 'Azure'
For each set of subcategories, I created an enum with possible static values:
public enum RedSubCategory
{
MAROON, ORANGE, BORDEAUX;
}
public enum BlueSubCategory
{
LIGHT, DARK, AZURE;
}
I'm now struggling with creating the Category class. Because each category is well defined, I thought of making it an enum. However, each enum value should have a list of subcategories of a different sub-type and this won't work:
public enum Category
{
RED, BLUE;
private List<SubCategory> subCategories;
// This won't work! SubCategory is not a real class and the
// SubCategory enums can't extend from a common supertype!
public setSubCategories(List<SubCategory> subCategories)
{
this.subCategories : subCategories;
}
}
I run into the same problem if I make Category a class instead of an enum. I can solve this using generics, but I lose the static definition of all possible categories. Additionally, how do I make it so the category RED is the only category that can use RedSubCategories? By using a class with generics, nothing would stop me from making a category object with category "blue" and red subcategories!
Are there any patters or inheritance/generics tricks I can use here?
May be using java interface:
package basic;
import static basic.SubCatMain.RedSubCategory.*;
import static basic.SubCatMain.BlueSubCategory.*;
public class SubCatMain {
public interface Category {
}
public enum MainCategory implements Category {
RED(MAROON, ORANGE, BORDEAUX),
BLUE(LIGHT, DARK, AZURE);
final private Category[] subcategories;
private MainCategory(Category... subcategories) {
this.subcategories = subcategories;
}
public Category[] getSubcategories() {
return subcategories;
}
}
public enum RedSubCategory implements Category {
MAROON, ORANGE, BORDEAUX;
}
public enum BlueSubCategory implements Category {
LIGHT, DARK, AZURE;
}
public static void main(String[] args) {
// do something with your categories
}
}
EDIT
#user1884155: about "I want to ensure a product cannot have a red category but a list of blue subcategories".
I don't think you can do that using only casting at compile time. The compiler can't know which main category will be provided to the Product ctor.
Coding some business rule will be required, something like:
public static class Product {
final private MainCategory category;
private Category[] categories;
public Product(MainCategory category) {
this.category = category;
}
protected void checkCategoriesAreValid(Category... categories) {
// throw an exception if at least one of the categories
// is not valid in respect of some business rules
// by default any sub-category of a main one is ok
List refs = Arrays.asList(category.getSubcategories());
for(Category c:categories) {
if (!refs.contains(c)) {
throw new IllegalArgumentException("...");
}
}
}
public Category[] getCategories() {
return categories;
}
public void setCategories(Category... categories) {
checkCategoriesAreValid(categories);
this.categories = categories;
}
}
Related
Preface: I've already researched why "enum inheritance" is illegal in Java.
My problem is the following: given a class Recipe, I want its property category to be a list of constant values like APPETIZER, BREAKFAST, DESSERT, MAIN_COURSE, SOUP - I logically use enums for this.
My question then is: if I wanted each of this enums to have "children of their own" (for example: SWEET and SAVORY for BREAKFAST, or CAKE, MUFFIN and BISCUITS for DESSERT), so that:
Specifying the subcategory ("child enum") is mandatory (e.g. myRecipe.setCategory(DESSERT) should raise an exception);
Using a "child enum" from a different "family" is forbidden (e.g. SOUP.BISCUITS should raise an exception);
I should be able to access the "child enum" through dot notation (e.g. myRecipe.setCategory(DESSERT.CAKE)) - or other similar "lightweight" syntax.
I haven't been able to come up with any clean solution in Java to fit all three requisites.
Is there any "esoteric" design pattern for this?
How would you implement this?
You can do this:
class Recipe {
private final Meal meal;
private final MealCategory category;
public <T extends Meal> Recipe(T meal, MealCategory<T> category) {
this.meal = meal;
this.category = category;
}
}
abstract class Meal {}
class Breakfast extends Meal {}
class Dinner extends Meal {}
class MealCategory<T extends Meal> {}
class Cereal extends MealCategory<Breakfast> {}
class Meat extends MealCategory<Dinner> {}
public class Test {
public static void main(String[] args) {
Recipe r = new Recipe(new Breakfast(), new Cereal());
Recipe r2 = new Recipe(new Breakfast(), new Meat()); // compile time error
}
}
Simple Design
Create a class Category. Inside Category, declare all the enum classes.
public class Category
{
public enum APPETIZER
{
}
public enum BREAKFAST
{
SWEET,
SAVORY
}
public enum DESSERT
{
CAKE,
MUFFIN,
BISCUITS
}
public enum MAIN_COURSE
{
}
}
Inside the Recipe class, category should be of type DESSERT. I have static imported Category class.
public class Recipe
{
DESSERT category;
public void setCategory(DESSERT category)
{
this.category = category;
}
public static void main(String[] args)
{
Recipe myRecipe = new Recipe();
myRecipe.setCategory(DESSERT.BISCUITS);
// statements below give compile time errors
// myRecipe.setCategory(DESSERT);
// myRecipe.setCategory(BREAKFAST.SWEET);
}
}
Improvement
Convert Category into a marker interface. All the categories such as DESSERT, BREAKFAST, etc. should implement Category.
interface Category {}
enum APPETIZER implements Category
{
}
enum BREAKFAST implements Category
{
SWEET,
SAVORY
}
enum DESSERT implements Category
{
CAKE,
MUFFIN,
BISCUITS
}
enum MAIN_COURSE implements Category
{
}
Make Recipe generic.
public class Recipe <T extends Category>
{
T category;
public void setCategory(T category)
{
this.category = category;
}
public static void main(String[] args)
{
Recipe<DESSERT> myRecipe = new Recipe<>();
myRecipe.setCategory(DESSERT.BISCUITS);
// statements below give compile time errors
// myRecipe.setCategory(DESSERT);
// myRecipe.setCategory(BREAKFAST.SWEET);
}
}
These are not design patterns. They are self implementation.
Lets say we have a product of different types, the different types have different internal variables and does not share anything beside the fact they are all products.
public interface Product {}
public class ProductA implements Product {
private String productAVariable;
// getters
}
public class ProductB implements Product {
private String productBVariable;
// getters
}
To get the quote/price of a product we must call an external service:
public interface QuoteService<T extends Product> {
ProductPrice getQuote(T product);
}
public class ProductAQuoteService implements QuoteService<ProductA> {
#Override
public ProductPrice getQuote(ProductA product) {
return new ProductPrice(someExternalService.getQuote(product.getProductAVariable()));
}
}
public class ProductBQuoteService implements QuoteService<ProductB> {
#Override
public ProductPrice getQuote(ProductB product) {
return new ProductPrice(someOtherExternalService.getQuote(product.getProductBVariable()));
}
}
So far so good. The last step is to iterate through a List of Products in our shoppingcart. Fetching the price/quote for each one. But how can we do that in a generic way? Currently i have some if-else instance of statements, but I feel there must be something more generic?
List<Product> productsInCart = Lists.asList(new ProductA(), new ProductB());
for (Product product : productsInCart) {
if (product instanceof ProductA) {
productAQuoteService.getQuote((ProductA) product);
} else if (product instanceof ProductB) {
productBQuoteService.getQuote((ProductB) product);
}
}
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;
}
}
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".
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.
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:
Enumerate categories on item's side - this is what your code does in both cases, or
Enumerate items on category's side - this would build an enum of categories, and attach a full list of items to each of them, or
Enumerate item+category pairs independently - this approach may be useful when storing item/category mapping in the database or in a configuration file.
I 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.
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,
we can easily ask about additional subcategories without writing the code for it: boolean isVegetableLiquid = VEGETABLES.is(LIQUIDS);
we can easily assign not only one, but multiple categories to an item as you can see for FRESH_SQUEEZED_ORANGE_JUICE.
we are using EnumSet and EnumMap for performance, including their methods like contains.
we absolutely are minimizing the amount of code required to add an additional item. This could be further minimized by setting this up by database or configuration. However, in that case we would have to avoid the use of Enum as well.
I have a class called SalesOrder (SO), that allows users to buy several items in a single order. SO has an order number.
class SalesOrder {
public String orderNumber;
}
Each SO has many items in it, so I have created a new class OrderItem which has the item name and price.
class OrderItem {
public String name;
public double price;
}
Each SO has a order header, include user name and address. It also has a field called total price, which hold the sum of all items prices
class OrderHeader {
public String username;
public String address;
public double totalPrice;
}
After that, I added two fields to SO:
class SalesOrder {
...
public List<OrderItem> items;
public OrderHeader header;
}
Because OrderItem and OrderHeader are always used with SalesOrder and the header should return all items prices, I converted them to be be inner classes of SalesOrder.
class SalesOrder {
...
public SalesOrder() {
this.items = new ArrayList<>();
this.header = new OrderHeader();
}
public class OrderItem {
...
}
public class OrderHeader {
...
public double getTotalPrice() {
double total = 0.0;
// loop SalesOrder.items
total += items[i].price;
return total;
}
}
}
My question is whether using inner classes like this is good OOP design? If not, how should they be designed?
======= Update Some information =======
I'm very sorry that I haven't give more inforamtion.
Header and Item make they construe method private, other object can't create them without SalesOrder.
SalesOrder have a factory method
class SalesOrder {
...
public SalesOrder parseOrder(Xml xml) {
//init header and items from xml
this.header = new OrderHeader(valueFromXml, valueFromXml);
}
public class OrderHeader {
....
private OrderHeader(username, address) { ... }
}
public Class OrderItem {
...
private OrderItem(name, price) { ... }
}
}
And other object use them like this
Xml xml = orderXmlData;
SalesOrder order = SalesOrder.parseOrder(orderXmlData);
OrderItem item = order.item;
OrderHeader header = order.header;
There are a few suggestion I would have that might improve your design. Firstly, it seems unlikely to me that the totalPrice should be part of the header. It seems more likely that it is derived from the order items rather than being a component of the header. Secondly, unless you want clients of the class to create order items independent of the order then there seems no need to define them as a class. Better to convert to an interface returned from the order. Thirdly, there's no reason why Header can't be interface - this allows a client to use any class they want as a header as long as it has name and address.
So my suggestion would be something like:
class Order {
interface Item {
String getName();
double getPrice();
}
interface Header {
String getName();
Address getAddress();
}
public Order(Header header) {
...
}
public double getTotalPrice() {
return streamItems().mapToDouble(Item::getPrice).sum();
}
public void addItem(String name, double price) {
...
}
public Stream<Item> streamItems() {
...
}
}
The use of nested classes depends on the requirements. I do not see any need for the use of nested classes in your code. If it is a Java Beans class, then you should keep the classes separated so they can be reusable. Other than your last block of code with the nested classes, your design is perfect.
I am currently struggling to figure out this problem for my project. I currently have a Food class that stores name, price and description with getters and setters and a toString. And a course class with subclasses (starter, main dessert). I am trying to figure out how to attach a Food to a Course.
public abstract class Course{
//fields
//protected only accessible to subclasses
protected MenuList starter;
protected MenuList main;
protected MenuList dessert;
protected MenuList drinks;
//Constructor
public Course(){
starter = new MenuList();
main = new MenuList();
dessert = new MenuList();
drinks = new MenuList();
}
//getters and setters
//methods
public abstract MenuList getList();
//add item
public void addItem(String course, String foodName, double price, String description, int calories){
this.addItem(course, foodName, price, description, calories);
}
}
starter subclass its the same with main and dessert subclasses
public class StarterFood extends Course{
//fields
//constructor
public StarterFood(){
//course,
starter.addItem("starter", "chicken wings", 2.30, "very nice", 150, false);
}
#Override
public MenuList getList() {
return starter;
}
//Constructors
//getters and setters
//methods
}
so far ive:
adding food (with a name, price, description, calories)
listing all food items
adding courses
searching for a course (by course number or name)
listing all courses
I only need to do this but I'm struggling any help is appreciated
attaching food to courses
If your trying to add a Food to a Course, you should use a "has a" relationship for example:
public class Course {
private Food food;
public Course(Food food) {
this.food = food;
}
public Course() {
}
public Food getFood() {
return this.food;
}
public void setFood(Food food) {
this.food = food;
}
}
I also wouldn't use StarterFood to extend a Course, because extends is for and "is a" relationship, I would call it StarterCourse and then add a default food for that course in the constructor.
public class StarterCourse extends Course {
public StarterCourse(Food food) {
// here call the super classes constructor
// add items via the Course constructor
super(food);
}
}
Then in your main class to test it out try this:
public class Main() {
public static void main() {
// First create new Food object
Food food = new Food();
// Create a new StarterCourse and add the Food object to it
StarterCourse starterCourse = new StarterCourse(food);
}
}