Enumerations1 are often associated with procedural code rather than object-oriented code. They tend to give rise to similar switch statements scattered through the code, and in general, these are replaced by polymorphism in object-oriented code.
For example, see Replace Type Code with Class, Replace Type Code with Subclasses, and Replace Type Code with State/Strategy in Refactoring by Martin Fowler. Closely related, see Replace Conditional With Polymorphism in the same volume; Bob Martin also has quite a bit to say on the disadvantages of switch statements in Clean Code (for example, heuristic G23 Prefer Polymorphism to If/Else or Switch/Case).
However, since object-orientation, like any other good paradigm, can be a powerful tool but is not a silver bullet, are there times when using an enumeration is a good decision?
1I use this term broadly; not just for something which is strictly an enum
in a C-based language, but for any set of entities that are used to represent it (a class with a set of static members, etc…).
7
PEP 435 was the proposal to add enumerations to Python. The motivation for adding enumerations:
The properties of an enumeration are useful for defining an immutable, related set of constant values that may or may not have a semantic meaning. Classic examples are days of the week (Sunday through Saturday) and school assessment grades (‘A’ through ‘D’, and ‘F’). Other examples include error status values and states within a defined process.
Enumerations serve the same purpose in object-oriented code as in procedural code.
0
As you said, enumerations tend to give rise to case discrimination … after all, how else are you going to make sense of the different values of the enumeration?
But in OOP, we already have a perfectly good way of doing case discrimination: polymorphic message dispatch. That’s all you need. You don’t need case
or switch
or even if
or for
or while
. Smalltalk doesn’t have any of those, it doesn’t have any conditionals, it doesn’t have any loops, it doesn’t have any case discrimination. Only polymorphic message dispatch. And it works just fine that way.
So, where you would do something like this in a language without polymorphic message dispatch:
enum Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
def weekdayToString(day: Weekday) = match day {
case Weekday.Monday => "Monday"
case Weekday.Tuesday => "Tuesday"
case Weekday.Wednesday => "Wednesday"
case Weekday.Thursday => "Thursday"
case Weekday.Friday => "Friday"
case Weekday.Saturday => "Saturday"
case Weekday.Sunday => "Sunday"
}
def printWeekday(day: Weekday) = println(weekdayToString(day))
printWeekday(Weekday.Wednesday)
// Wednesday
In a language with polymorphic message dispatch, you would do it like this:
sealed trait Weekday
object Monday extends Weekday { override def toString() = "Monday" }
object Tuesday extends Weekday { override def toString() = "Tuesday" }
object Wednesday extends Weekday { override def toString() = "Wednesday" }
object Thursday extends Weekday { override def toString() = "Thursday" }
object Friday extends Weekday { override def toString() = "Friday" }
object Saturday extends Weekday { override def toString() = "Saturday" }
object Sunday extends Weekday { override def toString() = "Sunday" }
def printWeekday(day: Weekday) = println(day)
printWeekday(Wednesday)
// Wednesday
(That is actually runnable Scala code.)
In other words: you can always replace an enumeration with case discrimination with an inheritance hierarchy with polymorphism.
Interestingly, Scala actually does have enumerations, but there are efforts for removing them from the language. They just don’t add any significant expressive power to a language which already has objects.
5
Enums have basically two purposes:
- They help you enforce type safety
- They can act as a container for
mutually-exclusive constants
Given those purposes, of what use are enums, even in object oriented languages?
In my opinion coding is all about finding the right way to express something in a specified context.
Often you don’t have the need for a fully blown class. You don’t introduce a class if you need to encapsulate constant, static data without or with only minimal behaviour. An enum is a better way to express your semantical needs rather than to derive it from the type of subclass, which is pretty much the only alternative involving polymorphism (and leads to switch statements on the types of subclasses as well).
Further, you keep control over the available values all the time. You cannot just put good old Liskov to use and sneak in another subclass with new behaviour. In some scenarios, that’s a very much desired behaviour. One should also note that in some languages like Java enums can carry behaviour. So they can bring the behaviour you want right to the place you want it to be. As Jon Skeet put it:
An enum in Java is a fixed set of objects, basically. The benefit is
that you know that if you have a reference of that type, it’s always
either null or one of the well-known set.
Practically speaking, in most cases where you’d just return arbitrary well-defined values (like error codes) you should switch to an enum to enforce type safety. This way you limit the amount of failure that is introduced by returning a wrong value by accident as the compiler can check it. Especially if there’s little need for behaviour and the associated data is static.
4
. . . are there times when using an enumeration is a good decision?
I used an enum
(via C#) to define a fixed, ordered set of “tasks”. The underlying integer values defined order. A wrapping class used the enum for instance equality. A collection class used the enum for sorting and enforcing uniqueness w/in the collection. These classes replaced code that was hopelessly broken functionally and conceptually.
That enum
was effectively a “core” data structure at the heart of higher-functioning composite data structures. In capturing essence of the domain objects at the core the composites were clean and the business functionality was delightfully concise.
Yes, there are valid uses of enumerations, even in systems which are primarily object-oriented.
In a layered architecture, an enumeration can be valuable in keeping. the separation of concerns between layers or modules. For example, a lexer and a parser may talk to each other in tokens, would could be some sort of enumeration.
Similarly, in a service-oriented architecture, when there is a reason for some sort of type code to cross multiple services and this cannot accomplished with multiple events instead, there may be good reason to use an enumeration.
On a little different note, it may be a useful temporary measure in emergent design in which you are waiting to make sure you get the abstract right before moving to a polymorphic solution.
But still, use enumerations with care.
15
I have written a comprehensive article on the subject. If you are interested please find the link here.
https://betterprogramming.pub/when-to-and-when-not-to-use-enums-in-java-8d6fb17ba978
1
Swift has enums. Swift enums are more like C unions on steroids. In the simplest case they are just like C enums, optionally without programmer-specified values, and optionally using strings instead of programmer-specified values.
A Swift enum cannot contain an undefined value. The programmer can specify whether an enum can be changed in a future library version, so type safety is guaranteed even with future versions (there is a special “default” case which means “any case that isn’t defined in the current version).
A Swift enum is basically a value with variants. Absolutely not the same thing as an object. An enum itself can have functions (which are likely to contain switch statements), the enum variants can have their own functions, but there is no overloading. Unlike objects with subclassing, the variants in an enum are totally unrelated. For example you could have an enum “DataSource” with variants string, file, path, url. You wouldn’t have an object using an abstract data source and have four subclasses for four different data sources, you would have ONE object with ONE DataSource enum.