TL; DR: Por lo general, es una mala idea usar una colección de enumeraciones, ya que a menudo conduce a un mal diseño. Una colección de enumeraciones generalmente requiere entidades del sistema distintas con lógica específica.
Es necesario distinguir entre algunos casos de uso de enum. Esta lista es solo la parte superior de mi cabeza, por lo que podría haber más casos ...
Todos los ejemplos están en C #, supongo que su lenguaje de elección tendrá construcciones similares o podría implementarlas usted mismo.
1. Solo un valor único es válido
En este caso, los valores son exclusivos, p. Ej.
public enum WorkStates
{
Init,
Pending,
Done
}
No es válido tener un trabajo que sea a la vez Pendingy Done. Por lo tanto, solo uno de estos valores es válido. Este es un buen caso de uso de enum.
2. Una combinación de valores es válida
Este caso también se llama banderas, C # proporciona un [Flags]atributo de enumeración para trabajar con estos. La idea se puede modelar como un conjunto debool so bits con cada uno correspondiente a un miembro enum. Cada miembro debe tener un valor de poder de dos. Se pueden crear combinaciones usando operadores bit a bit:
[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
}
El uso de una colección de miembros de enumeración es una exageración en tal caso, ya que cada miembro de enumeración solo representa un bit que está activado o desactivado. Supongo que la mayoría de los idiomas admiten construcciones como esta. De lo contrario, puede crear uno (por ejemplo, usarlo bool[]y abordarlo (1 << (int)YourEnum.SomeMember) - 1).
a) Todas las combinaciones son válidas.
Si bien estos están bien en algunos casos simples, una colección de objetos puede ser más apropiada ya que a menudo necesita información adicional o comportamiento basado en el tipo.
[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);
}
}
(nota: esto supone que solo te importan realmente los sabores del helado, que no necesitas modelar el helado como una colección de bolas y un cono)
b) Algunas combinaciones de valores son válidas y otras no
Este es un escenario frecuente. El caso a menudo podría ser que estás poniendo dos cosas diferentes en una enumeración. Ejemplo:
[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; }
}
Ahora, si bien es completamente válido para ambos Vehicley Buildingpara tener Doors y Windows, no es muy común que Buildings tengaWheel s.
En este caso, sería mejor dividir la enumeración en partes y / o modificar la jerarquía de objetos para lograr el caso # 1 o # 2a).
Consideraciones de diseño
De alguna manera, las enumeraciones tienden a no ser los elementos impulsores en OO ya que el tipo de entidad puede considerarse similar a la información que generalmente proporciona una enumeración.
Tome, por ejemplo, la IceCreammuestra del n. ° 2, la IceCreamentidad en lugar de banderas tendría una colecciónScoop objetos.
El enfoque menos purista sería Scooptener una Flavorpropiedad. El enfoque purista sería que el Scoopser una clase base abstracta para VanillaScoop, ChocolateScoop, ... clases en su lugar.
La conclusión es que:
1. No todo lo que es un "tipo de algo" debe ser enumeración
2. Cuando algún miembro de enumeración no es un indicador válido en algún escenario, considere dividir la enumeración en varias enumeraciones distintas.
Ahora para su ejemplo (ligeramente cambiado):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
Creo que este caso exacto debería modelarse como (nota: ¡no es realmente extensible!):
public class User
{
public bool IsAdmin { get; set; }
}
En otras palabras, está implícito que el Useres un User, la información adicional es si él es unAdmin .
Si se llega a tener múltiples roles que no son exclusivos (por ejemplo, Userpuede haber Admin, Moderator, VIP, ... al mismo tiempo), que sería un tiempo bueno para usar cualquiera de las banderas de enumeración o una clase base abtract o interfaz.
El uso de una clase para representar a Roleconduce a una mejor separación de responsabilidades donde un Rolepuede tener la responsabilidad de decidir si puede realizar una acción determinada.
Con una enumeración, necesitaría tener la lógica en un solo lugar para todos los roles. Lo cual derrota el propósito de OO y lo lleva de vuelta al imperativo.
Imagine que a Moderatortiene derechos de edición y Admintiene derechos de edición y eliminación.
Enum enfoque (llamado Permissionspara no mezclar roles y permisos):
[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;
}
}
Enfoque de clase (esto está lejos de ser perfecto, idealmente, desearía IActionun patrón de visitante pero esta publicación ya es enorme ...):
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));
}
}
Sin embargo, el uso de una enumeración puede ser un enfoque aceptable (por ejemplo, rendimiento). La decisión aquí depende de los requisitos del sistema modelado.