TL; DR: Di solito è una cattiva idea usare una collezione di enumerazioni poiché spesso porta a un cattivo design. Una raccolta di enum di solito richiede entità di sistema distinte con una logica specifica.
È necessario distinguere tra alcuni casi d'uso di enum. Questo elenco è solo in cima alla mia testa, quindi potrebbero esserci più casi ...
Gli esempi sono tutti in C #, immagino che il tuo linguaggio di scelta avrà costrutti simili o sarebbe possibile per te implementarli da solo.
1. È valido solo un valore singolo
In questo caso, i valori sono esclusivi, ad es
public enum WorkStates
{
Init,
Pending,
Done
}
Non è valido avere un lavoro che sia sia Pending
e Done
. Pertanto, solo uno di questi valori è valido. Questo è un buon caso d'uso di enum.
2. È valida una combinazione di valori
Questo caso è anche chiamato flag, C # fornisce l' [Flags]
attributo enum per lavorare con questi. L'idea può essere modellata come un insieme di bool
s o bit
s con ciascuna corrispondente a un membro enum. Ogni membro dovrebbe avere un valore di potenza di due. Le combinazioni possono essere create usando operatori 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
}
L'uso di una raccolta di membri enum è un problema eccessivo in tal caso, poiché ciascun membro enum rappresenta solo un bit impostato o non impostato. Immagino che la maggior parte delle lingue supportino costrutti come questo. Altrimenti puoi crearne uno (es. Usalo bool[]
e indirizzalo entro (1 << (int)YourEnum.SomeMember) - 1
).
a) Tutte le combinazioni sono valide
Anche se questi sono ok in alcuni casi semplici, una raccolta di oggetti può essere più appropriata in quanto spesso sono necessarie ulteriori informazioni o comportamenti in base al 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: questo presuppone che ti interessi davvero solo ai sapori del gelato - che non hai bisogno di modellare il gelato come una raccolta di palette e un cono)
b) Alcune combinazioni di valori sono valide e altre no
Questo è uno scenario frequente. Il caso potrebbe essere che stai mettendo due cose diverse in un enum. Esempio:
[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; }
}
Ora, mentre è completamente valido per entrambi Vehicle
e Building
per avere Door
s e Window
s, non è molto normale per Building
s avere Wheel
s.
In questo caso, sarebbe meglio suddividere l'enum in parti e / o modificare la gerarchia degli oggetti per ottenere il caso n. 1 o n. 2a).
Considerazioni sul design
In qualche modo, gli enum tendono a non essere gli elementi trainanti in OO poiché il tipo di entità può essere considerato simile alle informazioni che sono di solito fornite da un enum.
Prendi ad esempio il IceCream
campione dal n. 2, l' IceCream
entità invece delle bandiere avrebbe una raccolta di Scoop
oggetti.
L'approccio meno purista sarebbe per Scoop
avere una Flavor
proprietà. L'approccio purista sarebbe per la Scoop
di essere una classe base astratta per VanillaScoop
, ChocolateScoop
, ... classi invece.
La linea di fondo è che:
1. Non tutto ciò che è un "tipo di qualcosa" deve essere enum
2. Quando un membro enum non è un flag valido in alcuni scenari, considerare di dividere l'enum in più enum distinti.
Ora per il tuo esempio (leggermente modificato):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
Penso che questo caso esatto dovrebbe essere modellato come (nota: non realmente estensibile!):
public class User
{
public bool IsAdmin { get; set; }
}
In altre parole - è implicito, che User
è un User
, l'informazione aggiuntiva è se è un Admin
.
Se si arriva ad avere più ruoli che non sono esclusivi (ad esempio, User
può essere Admin
, Moderator
, VIP
, ... allo stesso tempo), che sarebbe un buon momento per utilizzare sia le bandiere enum o una classe base abtract o interfaccia.
L'uso di una classe per rappresentare una Role
porta a una migliore separazione delle responsabilità in cui una Role
può avere la responsabilità di decidere se può fare una determinata azione.
Con un enum dovresti avere la logica in un posto per tutti i ruoli. Che sconfigge lo scopo di OO e ti riporta all'imperativo.
Immagina che a Moderator
abbia diritti di modifica e Admin
abbia diritti sia di modifica che di eliminazione.
Approccio Enum (chiamato Permissions
per non mescolare ruoli e autorizzazioni):
[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;
}
}
Approccio di classe (questo è tutt'altro che perfetto, idealmente, vorresti il IAction
modello in un visitatore ma questo post è già 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));
}
}
L'uso di un enum può tuttavia essere un approccio accettabile (ad es. Performance). La decisione qui dipende dai requisiti del sistema modellato.