By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.

Enums in Java: From constants to the State pattern

Hipólito Alanis
September 12, 2023

Context

An enum is a type of class that can group constants in a single type. They are used when different constants can be referenced for a related reason (for example, a Status can be “DONE”, “REJECTED” or “PROCESSING”). Even though an Enum starts only as Constants, Java lets us use them in a smarter way, enabling us to use Object Oriented Patterns like State.

"Smart" Enums are Enums that implement functions defined as abstract in the same reach of the enum. An abstract function in an enum has to be implemented by all of the instances on the enum, much like abstract methods in a class when being inherited. 

From constants to the State pattern

The use of enums is really common in Java to define things like tags or classify states for an object. Let’s use Payment as an example. A Payment that can be in three different states, “In Progress”, “Validated”, “Canceled”. This representation is enough for the first iteration of our implementation, letting us store the status on a DB, transferring it as a constant, etc. But let’s assume that as time goes on, we start to have a lot more requirements out of our status logic.  If we had to define different behaviors for a Payment depending on the state, for example, a function that wants to generate a message depending on the state of the Payment. When we want to define this behavior, a simple solution would be to implement a switch statement as following.


public String getMessage(Payment payment) {
…

switch (payment.getPaymentStatus) {
case PaymentStatus.IN_PROGRESS:
return “The Payment is in progress”;
case PaymentStatus.VALIDATED:
return “The Payment is completed”;
case PaymentStatus.CANCELED:
return “The Payment is canceled”;
} throw RuntimeException.ofBadRequest(“Invalid status”);
}

Now, let’s assume that instead of just one implementation that changes by the payment status, there are multiple scenarios that change. We have to re-write several switch statements. If we then have to add a fourth type of PaymentStatus called “REJECTED”, then we have to search for all the switch that use PaymentStatus to add a fourth case.

To avoid this unreadable way to handle the State of the Payment, we can simplify the code if we let the Enums themselves handle the logic for each scenario that could require a switch.

Solution

For example if there’s a function that wants to implement a message that depends on the state, we can define a function “getMessage()” and let each enum handle the logic.


enum PaymentStatus {
IN_PROGRESS {
@Override
public String getMessage() {
return “This Payment is in progress”;
}
},
VALIDATED {
@Override
public String getMessage() {
return “This Payment is completed”;
}

},
CANCELED {
@Override
public String getMessage() {
return “This Payment is canceled”;
}

};

public abstract String getMessage();
}


Whenever we want to obtain the message of a state, we only need to do:


String message = payment.getPaymentStatus().getMessage();


This gives us three advantages.

  1. We don’t need to implement any behavior for a default or include errors in the switch. When we call getMessage(), we are calling an instance of the enum that has a defined type. We can never run into an ‘unknown enum’.
  2. If we add a new type of enum for PaymentStatus, we will be forced to complete the functions defined as abstract. We can’t run into a scenario where any of the enum can’t answer the function.
  3. If we want to add behavior for an enum, we can concentrate the code in the class adding functions instead of running around with switch statements.

And there are multiple useful implementations for Smart Enums beyond the listed. An example would be double-dispatching.

Conclusion

Smart enums are an easier way to implement State Patterns. Since the functions are like any other functions, this approach can be combined with other patterns like Visitor or Strategy if needed, giving us a lot of flexibility for the implementations.

Interested in our services?
Please book a call now.