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 Pending
e 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 bool
s ou bit
s, 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 Vehicle
e Building
tenha Door
s e Window
s, não é muito comum que Building
s tenha Wheel
s.
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 IceCream
amostra do item 2, a IceCream
entidade teria, em vez de sinalizadores, uma coleção de Scoop
objetos.
A abordagem menos purista seria a Scoop
de ter uma Flavor
propriedade. A abordagem purista seria o Scoop
de 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, User
pode 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 Role
leva a uma melhor separação de responsabilidades, onde a pessoa Role
pode 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 Moderator
possui direitos de edição e Admin
possui direitos de edição e exclusão.
Abordagem enum (chamada Permissions
para 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 IAction
padrã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.