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.
Related
Lets say I have a class to model an item in a game like so:
public class Item {
private final EnumItem type;
public Item(EnumItem type) {
this.type = type;
}
public Item(String name) {
this.type = EnumItem.fromName(name);
}
}
public enum EnumItem {
MACHINE_GUN("machine_gun"),
SWORD("sword"),
BAT("bat"),
DEFAULT("default");
private final String name;
public EnumItem(name) {
this.name = name;
}
public String getName() { return name; }
public static EnumItem fromName(String name) {
for(EnumItem i: EnumItem.values()) {
if(i.name.equals(name)) {
return i;
} else {
return EnumItem.DEFAULT;
}
}
}
}
Assume that .equals() and .hashCode() of Item are overridden correctly to compare the internal Enum.
Now I want a way to distinguish these items with a getter in Item: should I return an Enum or the String name? Is it good practice to return an Enum in general? Or is there a better way to distinguish these Items? Because returning the enum kind of looks like exposing the rep to me and I don't want my colleagues to use EnumItem directly to compare Items.
The approaches I thought of are the following:
string getName() to do something like item1.getName().equals("machine_gun");
EnumItem getEnum() to do item1.getEnum().equals(EnumItem.MACHINE_GUN);
item1.equals(new Item("machine_gun"));
static name(String name) { new Item(name) } to do item1.equals(Item.name("machine_gun"));
I don't know what should I do, I'd appreciate some insight from experienced programmers.
I know they look like they would from context, but in my use case these items have no special functionality that would justify extending from the base Item class.
Is this good practice? Sure, you're using aggregation since Item doesn't depend on EnumItem, which is fine. That being said, could it be done better? Sure. Is the alternative I provide the only solution? No.
Alternative
If you want this to be extensible, consider using an interface to represent an item. Then allow the interface to extend this interface to provide some standard types. Alternatively you could use composition or aggregation to define a type inside EnumItem that implements the Item interface to ensure that equals/hashcode for the Item are always override and adhere to some contract.
interface Item {
String key();
}
enum EnumItem implement Item {
private final String key;
EnumItem(String key) {
this.key = key;
}
#Override
public String key() {
return key;
}
}
class AbstractItem implements Item {
// constructor, override name()
}
Item item = EnumItem.FOO_BAR;
Item item2 = new AbstractItem("FooBar");
Item item3 = () -> "FooBar";
What is the best way to store a Collection<MyItem> ? This collection is effectively static for current user. Each user can only see their collection. MyItem item implements IItem:
public interface IItem {
public Integer getItemID();
public void setItemID(Integer id);
public String getTitle();
public void setTitle(String title);
/*more getters and setters*/
public IItem parseServerResponse(String response);
public int postItem(); //posts this IItem to server, return ok ->200, unauth->401, etc
public IItem findItem(String[] filters);
/*more advanced methods*/
}
I can store Collection<MyItem> elsewhere, but then I can't access private MyItem methods from CurrentMyItems:
public class CurrentMyItems{
private final List<IItem> allItemsList;
public CurrentMyItems(String allItemsServerResponseString){
JSONArray rawItems = parseResponse(allItemsServerResponseString);
int arrSize = rawItems.length()+estimateQuantityOfNewItems();
List<IItem> allItemsList = new ArrayList<>(arrSize);
for (int i = 0; i < Items.length(); i++) {
allItems.add(i, parseItem(Items.get(i)));
}
}
/*methods*/
}
Or inside of the MyItem class (see commented out options):
public class MyItem implements IItem {
/*
private final static List<IItem> allItemsStaticList = new ArrayList<>();
private final static Map<Integer, IItem> allItemsStaticMap = new HashMap<>();
private final List<IItem> allItemsList; //
private final static Map<Integer, IItem> allItemsMap;
*/
/*implemented methods*/
}
allItemsStaticList - stores a static list of all Items. Seems memory efficient, but what if I need to store separate collections of MyItems in future? This is highly unlikely, but still...
allItemsList - Same class has two distinct functions. It is either
storing a single Item, in which case allItemsList/Map = null;
or
allItemsList = new ArrayList<>();, while other fields are empty.
This seems OK, but it breaks the Least Surprise Principle.
Which approach to store a MyItemCollection is more natural?
Also, should I store Items as a Map or a List given that MyItem myItem = getMyItemByID(int id); is the main way to access MyItem?
Update
We can implement an Item class so that an instance can either hold a collection of Item instances or the modeled data, but not both.
public class Item {
private final Map<Integer, Item> itemsMap;
private final IntegerProperty itemID; // private final String[] names;
public Item(){
itemsMap = new HashMap<>();
itemID = null; //names = null;
}
private Item(Integer id) {
itemsMap= null;
itemID = new SimpleIntegerProperty(id); //names = new String[1];
}
public Item makeGenericItem(){
return itemsMap == null ? null : new Item(itemsMap.size());
}
// other methods, including getters and setters
}
But at what cost?.. This class violates single responsibility principle.
Conclusion - in most cases a Collection of Item instances should be stored outside of Item class.
In OOP the data elements of an object are also known as attributes of the object. So, you should ask yourself whether a collection of items is an attribute of an item or not.
For example, when you assume your items are students. Would you say that a list of students is an attribute of a student? -- Probably not, as a list of students is not part of a student. Instead a student is part of a list of students.
Since a list of students is not an attribute of students in real life, I would not model it differently in code just to make it technically more elegant.
The design of your classes should be driven by the structure of the domain that your are working in. When you need to decide where to put an attribute do not ask "does it make sense to put it here because of the features my programming language offers?" but ask "where does this attribute belongs to in my domain?".
So recently I have been trying to practice with hashing and using linkedlists in a table to store values. I understand the concept but I am having trouble putting it into practice and can't seem to find what I'm looking for online.
For example:
Let's say I wanted to use a hashtable to store things like for a computer such as the monitor, mouse, etc. I would want methods such as:
boolean addMonitor(String id, String description, double price, int units, String size)
boolean addMouse(String id, String description, double price, int units, int buttons)
I don't understand how to use these methods to store them in a hashtable. I would obviously like to use other methods to access and change the values within each later too. Any help is appreciated. Thank you.
Even if its name says "table", HashTable is not like a "database table" where you have columns, and each column store values... it seems that you want use hashtable as a database table.
Hashtable store objects! So your methods should look better like this:
public class Example {
public static void main(String[] args) {
ItemStore store;
Monitor monitor;
Mouse mouse;
store = new ItemStore();
monitor = new Monitor();
monitor.id = 2;
monitor.price = 6;
mouse = new Mouse();
mouse.id = 7;
mouse.buttons = 3;
store.addItem(monitor);
store.addItem(mouse);
System.out.println(store.getItem(2).price); // = 6
System.out.println(((Monitor) store.getItem(2)).dpi);
System.out.println(((Mouse) store.getItem(7)).buttons); //Downcasting ... = 3
}
public static class Item {
String id;
String description;
int price;
// common attributes here!
}
public static class Monitor extends Item {
private int dpi;
// monitor particular atributes here!!
}
public static class Mouse extends Item {
private int buttons;
// mouse particular attributes here!!!
}
public static class ItemStore {
private Hashtable<String, Item> table = new HashTable<>();
public boolean addItem(Item item) {
this.table.put(item.getId(), item);
}
public Item getItem(String id) {
return this.table.get(id);
}
}
}
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 want to make a Booking class to book concert tickets. A ConcertHall class has 2 different seats, namely VIP and Regular. I've chosen Seat[][] as my data structure, where a Seat class contains seat_number:String and preference:String. Now, the VIP and Regular seats have a different capacity. Let's say the VIP's capacity is 10x5 seats and the Regular's capacity is 50x100 seats. The seats corresponding to both VIP and Regular also have Left, Center, and Right preferences.
The problem with my current design is I have so many redundant code . Let's say a user wants to book a concert ticket, he/she will call the method: book("VIP", "Mary", "Center"). This is what my design and book method look like:
class Booking
{
private ConcertHall A;
public Booking(String name)
{
A = new ConcertHall(name);
}
public boolean book(String serviceClass, String name, String preference)
{
Seat seat = find(serviceClass, preference)
if(seat != null)
{
assignSeat(seat, name);
return true;
}
return false;
}
}
class ConcertHall
{
private Seat[][] VIP;
private Seat[][] Regular;
public Seat findSeat(String serviceClass, String preference)
{
if(serviceClass.equals("VIP"))
{
// query VIP array
}
else if(serviceClass.equals("Regular"))
{
// query Regular array
}
}
public boolean assignSeat(Seat seat, String name)
{
if(serviceClass.equals("VIP"))
{
// query VIP array
}
else if(serviceClass.equals("Regular"))
{
// query Regular array
}
}
}
There's already a problem here, namely for almost every method in ConcertHall, I have to do 2 identical checking for the VIP class and Regular class. Now I'm stuck. My code looks long and stupid because of the 2 identical checking.
===================================update===================================
I forgot to mention, there's an additional requirement. I have to keep track of the seats' position. Let's say a group of people want to book concert tickets, we have to find available seats in the same row. So, a HashMap wouldn't work here I think.
Factor out the identical array checking code into a method of its own and then pass it the correct lookup array. Something like
public Seat findSeat(String serviceClass, String preference)
{
if(serviceClass.equals("VIP"))
{
return findSeatIn(VIP, preference);
}
else if(serviceClass.equals("Regular"))
{
return findSeatIn(Regular, preference);
}
}
public Seat findSeatIn(Seat[][] seatArray, String preference)
{
// query seatArray for preference
}
Then repeat this with assignSeat() and assignSeatIn().
Also, findSeat() should ideally be findSeats() returning a list of seats matching a user's preference. The user should then be a able to choose one s/he likes the most, which should then be assigned by assignSeat(). Just a thought.
You can have a Map in your ConcertHall. In your check just do a get on map, query and assign. So you can do something like this
class ConcertHall{
private Map<String, Seat[][]> seatMap;
public Seat findSeat(String serviceClass, String preference){
Seat[][] seats = seatMap.get(serviceClass);
//query on seats
}
To use this, you need to put the seats in map appropriately. moreover you can use enum for the values like VIP and Regular
It seems to me that there is room (if I may say so) for an additional class that would represent individual regions of the hall; say:
class Zone {
Seat[][] seats;
public Zone(int rows, int columns) {
seats = new Seat[rows][columns];
}
public Seat findSeat(String preference) {
...
}
public boolean assignSeat(Seat seat, String name) {
...
}
}
This class could be sub-classed if different service classes would required a different behavior.
The ConcertHall class could be simpler:
class ConcertHall{
private Map<String, Zone> zoneMap = new HashMap<String, Zone>();
{
zoneMap.put("VIP", new Zone(5,10));
zoneMap.put("Regular", new Zone(50,100));
}
public Seat findSeat(String serviceClass, String preference) {
return zoneMap.get(serviceClass).findSeat(preference);
}
...
}
You need a good initial design in order to get anywhere with oop. Here is an alternate implementation you might want to consider:
class Booking {
public Booking(String personName, Seat seat) {
...
}
// getter and setters...blah blah
}
class ConcertHall {
// Making it protected allows for easy subclassing
protected List<Booking> vipSeating;
protected List<Booking> regularSeating;
public ConcertHall(String name, int capVip, int capReg) {
vip = new ArrayList<>();
regular = new ArrayList<>();
// initialise capacity, etc
}
public void addVipBooking(Booking b) throws ArrayIndexOutOfBoundsException {
// If there is room, add the person, else throw an exception
}
public void addRegularBooking(Booking b) throws ArrayIndexOutOfBoundsException {
// If there is room, add the person, else throw an exception
}
public boolean vipIsFull() {
// is the vip section full??
}
public boolean regularIsFull() {
// is the regular section full??
}
}