TL; DR: Geralmente, é uma má idéia usar uma coleção de enumerações, pois geralmente leva a um design inadequado. Uma coleção de enums geralmente requer entidades distintas do sistema com lógica específica.
É necessário distinguir entre alguns casos de uso de enum. Esta lista é apenas do topo da minha cabeça, então pode haver mais casos ...
Os exemplos estão todos em C #, acho que sua linguagem de escolha terá construções semelhantes ou seria possível implementá-las você mesmo.
1. Apenas um valor único é válido
Nesse caso, os valores são exclusivos, por exemplo
public enum WorkStates
{
Init,
Pending,
Done
}
É inválido ter algum trabalho que seja ambos Pendinge Done. Portanto, apenas um desses valores é válido. Este é um bom caso de uso de enum.
2. Uma combinação de valores é válida.
Este caso também é chamado de sinalizadores, o C # fornece o [Flags]atributo enum para trabalhar com eles. A ideia pode ser modelada como um conjunto de bools ou bits, cada um correspondendo a um membro da enumeração. Cada membro deve ter um valor de poder de dois. As combinações podem ser criadas 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
}
O uso de uma coleção de membros da enumeração é um exagero, pois cada membro da enumeração representa apenas um bit que está definido ou não. Eu acho que a maioria dos idiomas suporta construções como esta. Caso contrário, você pode criar um (por exemplo, use bool[]e resolva-o com (1 << (int)YourEnum.SomeMember) - 1).
a) Todas as combinações são válidas
Embora isso esteja correto em alguns casos simples, uma coleção de objetos pode ser mais apropriada, pois muitas vezes você precisa de informações ou comportamentos adicionais com base no 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: isso pressupõe que você realmente se preocupa apenas com os sabores do sorvete - que não precisa modelar o sorvete como uma coleção de bolas e um cone)
b) Algumas combinações de valores são válidas e outras não
Este é um cenário frequente. O caso pode ser que você esteja colocando duas coisas diferentes em uma enumeração. Exemplo:
[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; }
}
Agora, embora seja completamente válido para ambos Vehiclee Buildingtenha Doors e Windows, não é muito comum que Buildings tenha Wheels.
Nesse caso, seria melhor dividir o enum em partes e / ou modificar a hierarquia de objetos para alcançar o caso nº 1 ou nº 2a).
Considerações de design
De alguma forma, as enumerações tendem a não ser os elementos determinantes no OO, pois o tipo de uma entidade pode ser considerado semelhante às informações que geralmente são fornecidas por uma enumeração.
Pegue, por exemplo, a IceCreamamostra do item 2, a IceCreamentidade teria, em vez de sinalizadores, uma coleção de Scoopobjetos.
A abordagem menos purista seria a Scoopde ter uma Flavorpropriedade. A abordagem purista seria o Scoopde ser uma classe base abstrata para VanillaScoop, ChocolateScoop, ... aulas em seu lugar.
A linha inferior é que:
1. Nem tudo que é um "tipo de algo" precisa ser enum
2. Quando algum membro da enum não é um sinalizador válido em algum cenário, considere dividir a enum em várias enumerações distintas.
Agora, para o seu exemplo (ligeiramente alterado):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
Eu acho que esse caso exato deve ser modelado como (nota: não é realmente extensível!):
public class User
{
public bool IsAdmin { get; set; }
}
Em outras palavras - está implícito que Useré um User, a informação adicional é se ele é um Admin.
Se você começa a ter múltiplos papéis que não são exclusivos (por exemplo, Userpode ser Admin, Moderator, VIP, ... ao mesmo tempo), isso seria um bom momento para usar tanto bandeiras enum ou uma classe base abtract ou interface.
O uso de uma classe para representar uma Roleleva a uma melhor separação de responsabilidades, onde a pessoa Rolepode ter a responsabilidade de decidir se pode executar uma determinada ação.
Com uma enumeração, você precisa ter a lógica em um só lugar para todas as funções. O que derrota o objetivo do OO e o leva de volta ao imperativo.
Imagine que a Moderatorpossui direitos de edição e Adminpossui direitos de edição e exclusão.
Abordagem enum (chamada Permissionspara não misturar funções e permissões):
[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;
}
}
Abordagem de classe (isso está longe de ser perfeito, idealmente, você desejaria o IActionpadrão de visitante, mas este post já é 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));
}
}
Usar um enum pode ser uma abordagem aceitável (por exemplo, desempenho). A decisão aqui depende dos requisitos do sistema modelado.