Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constant specific methods for enum design decision

More of a design question this is. So to begin with, I've an enum:

enum WageType {
    MONTHLY {
        public int getWageRatePerUnit() {
            return 60000;
        }

        public boolean isPaymentTime(DateTime date) {
            return date.getDayOfMonth() == 29;
        }
    },
    DAILY {
        public int getWageRatePerUnit() {
            return 2500;
        }

        public boolean isPaymentTime(DateTime date) {
            return date.getHourOfDay() == 20;
        }
    };

    public abstract int getWageRatePerUnit();
    public abstract boolean isPaymentTime(DateTime date);
}

Now I've 2 abstract methods there, and respective implementation in the enum constants. The real scenario contains some sort of lengthy business logic there and I ended up with 3 or 4 such methods.

But I've a feeling that I'm messing up my enum. Is this considered to be bad design practice, to put business logic specific to different enum types in constant specific methods? What difference would it make if I rather implement an interface, and define methods there, and let all the enum constant implement them? Of course, I would have to use interface reference everywhere after that, but I've to see whether it works well with hibernate mapping or not. Currently I'm storing the enum constants as a part of entities using @Enumerated(EnumType.String). So, I would expect the other approach not to affect this part by much, i.e. End behaviour should remain same.

BTW, I don't think an interface would be require, as I'm not going to have a separate set of enum constants possessing similar behaviour. Other option might be some kind of utility class, to deal with these enum constants. And just pass the methods the type of enum it is dealing with.

What do you say? Which approach should I follow? I am open to any other better option if you can think of?

like image 799
Rohit Jain Avatar asked Nov 09 '22 19:11

Rohit Jain


1 Answers

Regarding making your enum implement an interface vs extending template methods for every enum value, I'd stick to the template methods, since, as you said, there won't be any other enum or class implementing those methods. (Implementing an interface would make sense if you had many enums or classes that need to fulfill a contract, but this doesn't seem to be the case).

I personally consider enums as classes with some constraints (i.e. they're final and stateless), but there are people out there arguing that using enums this way is hacky. I don't agree at all, because I think that using the features provided by the language is absolutely valid, and template methods are one of such features. However, it's good to know that this approach generates resistance in some conservative environments.

Despite this, I do think that using template methods in enums makes your code quite verbose. I'd only take this approach if both the number of template methods and the number of enum values is low and if it's granted they won't change in the future.

On the other hand, you could achieve exactly the same functionality by using this code instead of yours:

public enum WageType {
    MONTHLY(60000, 29), DAILY(2500, 20);

    private final int wageRatePerUnit;
    private final int hourOfDay;

    public WageType(int wageRatePerUnit, int hourOfDay) {
        this.wageRatePerUnit = wageRatePerUnit;
        this.hourOfDay = hourOfDay;
    }

    public int getWageRatePerUnit() {
        return this.wageRatePerUnit;
    }

    public boolean isPaymentTime(DateTime date) {
        return date.getHourOfDay() == this.hourOfDay;
    }
}

This code is much simpler than yours and provides exactly the same functionality. However, you wouldn't be able to retrieve your enum from the database, since Hibernate wouldn't find a default constructor for it.

Now, I believe that using available language features to come to a solution is OK, but using them because otherwise Hibernate wouldn't know how to materialize your enum... well, that's what I call a workaround.

Maybe you could make WageType an entity and let Hibernate handle it as usual, and associate it to other entities, and either implement your logic in methods of this entity or in a service class outside of the entity or in some utility class, whatever you prefer. Please be aware that this would lead to an extra join or select (or maybe a subselect, ugh), depending on every association's fetch strategy where the WageType entity participates.

like image 141
fps Avatar answered Nov 14 '22 21:11

fps