TL;DR: It is usually a bad idea to use a collection of enums as it often leads to a bad design. A collection of enums usually calls for distinct system entities with specific logic.
It is necessary to distinguish between a few use cases of enum. This list is only of top of my head so there might be more cases...
The examples are all in C#, I guess your language of choice will have similar constructs or it would be possible for you to implement them yourself.
1. Only single value is valid
In this case, the values are exclusive, e.g.
public enum WorkStates
{
Init,
Pending,
Done
}
It is invalid to have some work that is both Pending
and Done
. Therefore only one of these values is valid. This is a good use-case of enum.
2. A combination of values is valid
This case is also called flags, C# provides [Flags]
enum attribute to working with these. The idea can be modeled as a set of bool
s or bit
s with each corresponding to one enum member. Each member should have a value of power of two. Combinations can be created using bitwise operators:
[Flags]
public enum Flags
{
None = 0,
Flag0 = 1, // 0x01, 1 << 0
Flag1 = 2, // 0x02, 1 << 1
Flag2 = 4, // 0x04, 1 << 2
Flag3 = 8, // 0x08, 1 << 3
Flag4 = 16, // 0x10, 1 << 4
AFrequentlyUsedMask = Flag1 | Flag2 | Flag4,
All = ~0 // bitwise negation of zero is all ones
}
Using a collection of enum members is an overkill in such a case as each enum member only represents one bit that is either set or unset. I guess most of languages support constructs like this. Otherwise you can create one (e.g. use bool[]
and address it by (1 << (int)YourEnum.SomeMember) - 1
).
a) All combinations are valid
While these are ok in some simple cases, a collection of objects may be more appropriate as you often need additional information or behaviour based on the type.
[Flags]
public enum Flavors
{
Strawberry = 1,
Vanilla = 2,
Chocolate = 4
}
public class IceCream
{
private Flavors _scoopFlavors;
public IceCream(Flavors scoopFlavors)
{
_scoopFlavors = scoopFlavors
}
public bool HasFlavor(Flavors flavor)
{
return _scoopFlavors.HasFlag(flavor);
}
}
(note: this assumes you only really care about the flavors of the ice-cream - that you don't need to model the ice-cream as a collection of scoops and a cone)
b) Some combinations of values are valid and some not
This is a frequent scenario. The case might often be that you're putting two different things into one enum. Example:
[Flags]
public enum Parts
{
Wheel = 1,
Window = 2,
Door = 4,
}
public class Building
{
public Parts parts { get; set; }
}
public class Vehicle
{
public Parts parts { get; set; }
}
Now while it's completely valid for both Vehicle
and Building
to have Door
s and Window
s, it's not very usual for Building
s to have Wheel
s.
In this case, it would be better to break up the enum into parts and/or modify the object hierarchy in order to achieve either case #1 or #2a).
Design considerations
Somehow, the enums tend not to be the driving elements in OO since the type of an entity can be considered similar to the information that is usually provided by an enum.
Take e.g. the IceCream
sample from #2, the IceCream
entity would instead of flags have a collection of Scoop
objects.
The less purist approach would be for the Scoop
to have a Flavor
property. The purist approach would be for the Scoop
to be an abstract base class for VanillaScoop
, ChocolateScoop
, ... classes instead.
The bottom line is that:
1. Not everything that is a "type of something" needs to be enum
2. When some enum member is not a valid flag in some scenario, consider splitting the enum into multiple distinct enums.
Now for your example (slightly changed):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
I think this exact case should be modeled as (note: not really extensible!):
public class User
{
public bool IsAdmin { get; set; }
}
In other words - it is implicit, that the User
is a User
, the additional info is whether he is an Admin
.
If you get to have multiple roles that are not exclusive (e.g. User
can be Admin
, Moderator
, VIP
, ... at the same time), that would be a good time to use either flags enum or an abtract base class or interface.
Using a class to represent a Role
leads to a better separation of responsibilities where a Role
can have the responsibility to decide whether it can do a given action.
With an enum you'd need to have the logic in one place for all the roles. Which defeats the purpose of OO and brings you back to imperative.
Imagine that a Moderator
has edit rights and Admin
has both edit and delete rights.
Enum approach (called Permissions
in order not to mix roles and permissions):
[Flags]
public enum Permissions
{
None = 0
CanEdit = 1,
CanDelete = 2,
ModeratorPermissions = CanEdit,
AdminPermissions = ModeratorPermissions | CanDelete
}
public class User
{
private Permissions _permissions;
public bool CanExecute(IAction action)
{
if (action.Type == ActionType.Edit && _permissions.HasFlag(Permissions.CanEdit))
{
return true;
}
if (action.Type == ActionType.Delete && _permissions.HasFlag(Permissions.CanDelete))
{
return true;
}
return false;
}
}
Class approach (this is far from perfect, ideally, you'd want the IAction
in a visitor pattern but this post is enormous already...):
public interface IRole
{
bool CanExecute(IAction action);
}
public class ModeratorRole : IRole
{
public virtual bool CanExecute(IAction action)
{
return action.Type == ActionType.Edit;
}
}
public class AdminRole : ModeratorRole
{
public override bool CanExecute(IAction action)
{
return base.CanExecute(action) || action.Type == ActionType.Delete;
}
}
public class User
{
private List<IRole> _roles;
public bool CanExecute(IAction action)
{
_roles.Any(x => x.CanExecute(action));
}
}
Using an enum may be an acceptable approach though (e.g. performance). The decision here depends on the requirements of the modeled system.