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...).
-
3hmmmm....this is the first time I've heard enumerations are bad. I guess error-prone strings or magic numbers must be better?Dunk– Dunk09/17/2013 22:09:41Commented Sep 17, 2013 at 22:09
-
5@Dunk You will note that I carefully avoided saying enumerations are bad. I merely stated that they are more closely associated with procedural than object-oriented code, in general.Kazark– Kazark09/17/2013 22:35:20Commented Sep 17, 2013 at 22:35
-
4"Enumerations1 are typically associated with procedural code rather than object-oriented code." - this is nonsense for a start. Who told you this?James– James09/18/2013 09:35:32Commented Sep 18, 2013 at 9:35
-
2I tend to use enums for properties in objects that can take one of many different values. For instance, if I'm writing a packet parser, I'd want to know whether the packet was RX'd or TX'd, so I'll have a direction enum rather than (as @Dunk pointed out) magic numbers of error prone strings.Jamie Taylor– Jamie Taylor09/18/2013 10:24:46Commented Sep 18, 2013 at 10:24
-
3It is interesting to note that, when Nicklaus Wirth tackled object-oriented programming, in his Oberon language, enumerations were one of the things he dropped from the language, because he couldn't find an acceptable way to allow "derived objects" to extend the enumeration.John R. Strohm– John R. Strohm09/18/2013 12:40:25Commented Sep 18, 2013 at 12:40
7 Answers 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.
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.
-
4Jörg, I feel like your example is flawed. In Scala a sealed trait basically corresponds to a Java enum (which can also have behaviour). So it is nice that you can do that in Scala - but sealed trait is imho just another word for a java enum - what do you think?Falcon– Falcon09/18/2013 11:50:21Commented Sep 18, 2013 at 11:50
-
1I have to agree with @Falcon. In fact, it's possible to write idiomatic Java code that looks a lot like those
sealed trait
s... only usingEnum
s and overridingtoString()
, much like you did. No need to useswitch
with enums, either! If you must useswitch
, Enums in Java help with pseudo-"pattern matching": the compiler will warn you if you are missing "patterns".Andres F.– Andres F.09/18/2013 14:09:42Commented Sep 18, 2013 at 14:09 -
1+ You can iterate over enums whereas it's a bit harder and noisier to do the same thing with traitsFalcon– Falcon09/18/2013 14:15:34Commented Sep 18, 2013 at 14:15
-
My concern with this would be efficiency. Passing an object reference around might well be as fast as passing an enumeration around and only incur a slight extra overhead for marking those addresses as used, but I question whether dynamic method dispatch would be as fast as a
switch
. If nothing else, I expect having 7toString
methods compiled down (to e.g. bytecode or machine code) would use more memory space than a single function with aswitch
. The difference is probably negligable on a big modern processor, but on an embedded system it might be significant...Pharap– Pharap02/03/2024 05:45:53Commented Feb 3, 2024 at 5:45 -
... Though to be fair, one advantage of the dynamic method approach is that more values can be added at runtime. Though I suspect the cases where that would actually be useful are somewhat limited.Pharap– Pharap02/03/2024 05:47:42Commented Feb 3, 2024 at 5:47
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.
-
I'll note that "mutually exclusive" is generally not a requirement for enums, which can also be defined as bitfields in order to support multiple values.Aaronaught– Aaronaught09/18/2013 00:18:17Commented Sep 18, 2013 at 0:18
-
Constants are only "mutually exclusive" in that each needs to be unique. They can be combined very well into "sets of enum values". IE repeated calendar item occuring on every second tuesday and wednesday, would probably use a
set of weekday
(whereweekday
is the enum) for the (week)days on which the calendar item occurs.Marjan Venema– Marjan Venema09/18/2013 10:09:39Commented Sep 18, 2013 at 10:09 -
Imho enum values that are combined inherently into a bitset are impure and this feature should not be called enum anymore, but a bitset in which each bit represents something. The bits themselves can be enumerated. I know some C languages allow that and personally I don't like it.Falcon– Falcon09/18/2013 11:41:54Commented Sep 18, 2013 at 11:41
-
@Falcon I'm not sure I agree with 'impure' since such enumerations have well defined operations that correspond to the usual set operations, and there are for the most part no surprises, but I would agree that it would be semantically better for a language to distinguish between
enum
andset
/bitset
. To memory, most Pascal-family languages (e.g. Ada, Modula) do that, but C-derived languages (e.g. C++, C#) don't.Pharap– Pharap02/03/2024 05:56:37Commented Feb 3, 2024 at 5:56
. . . 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.
-
2@Falcon perfectly valid and encouraged. There's even a badge for it. See What is this "answer your own question" jazz? from MSO about it.user40980– user4098009/17/2013 21:38:16Commented Sep 17, 2013 at 21:38
-
4@Falcon: I quote this blog post: "To be crystal clear, it is not merely OK to ask and answer your own question, it is explicitly encouraged."Kazark– Kazark09/17/2013 21:39:04Commented Sep 17, 2013 at 21:39
-
3@Falcon
If you do it for reputation - that's bad.
-- Says who? It's OK to pimp rep if you're answering someone else's questions, but not your own?Robert Harvey– Robert Harvey09/17/2013 21:59:55Commented Sep 17, 2013 at 21:59 -
3That said, the answer is not all that great. It basically says "Sometimes they're good, but sometimes they're not." It doesn't really explain in detail the contexts under which an enumeration could be good or bad.Robert Harvey– Robert Harvey09/17/2013 22:01:20Commented Sep 17, 2013 at 22:01
-
2@RobertHarvey: To be fair, I don't think it's a particularly good question, seeing as how there's no elaboration whatsoever on why enums in OOP would be any different from enums in any other model (procedural, functional, and so on). The question could easily be changed to "when should I use enums, period?" and that might rightly be closed as too broad.Aaronaught– Aaronaught09/18/2013 00:21:43Commented Sep 18, 2013 at 0:21
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
-
Please do not post links without making a summary of what is behind it. If your links expires/changes, the server closes, ... Nobody will be able to have the answer.f222– f22204/22/2022 07:25:04Commented Apr 22, 2022 at 7:25
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.