TL; DR: di solito è una cattiva idea usare una raccolta di enumerazioni che spesso porta a un cattivo design. Una raccolta di enumerazioni di solito richiede entità di sistema distinte con una logica specifica.
È necessario distinguere tra alcuni casi d'uso di enum. Questa lista è solo in cima alla mia testa, quindi potrebbero esserci più casi ...
Gli esempi sono tutti in C #, immagino che la tua lingua di scelta avrà costrutti simili o sarebbe possibile implementarli tu stesso.
1. Solo il singolo valore è valido
In questo caso, i valori sono esclusivi, ad es.
public enum WorkStates
{
Init,
Pending,
Done
}
Non è valido avere un lavoro che sia sia Pending
che Done
. Pertanto solo uno di questi valori è valido. Questo è un buon caso d'uso di enum.
2. Una combinazione di valori è valida
Questo caso è anche chiamato flag, C # fornisce [Flags]
enum attributo a lavorare con questi. L'idea può essere modellata come un insieme di bool
s o bit
s con ogni corrispondente a un membro enum. Ogni membro dovrebbe avere un valore di potere di due. Le combinazioni possono essere create utilizzando 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'utilizzo di una raccolta di membri di enum è eccessivo in tal caso, poiché ciascun membro enum rappresenta solo un bit impostato o non impostato. Credo che la maggior parte delle lingue supportino costrutti come questo. Altrimenti puoi crearne uno (ad esempio, usa bool[]
e indirizzalo a (1 << (int)YourEnum.SomeMember) - 1
).
a) Tutte le combinazioni sono valide
Anche se in alcuni casi questi sono ok, una raccolta di oggetti potrebbe essere più appropriata in quanto spesso hai bisogno di informazioni o comportamenti aggiuntivi 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 solo i 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 spesso 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 sia per Vehicle
che Building
per avere Door
se Window
s, non è usuale 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 sulla progettazione
In qualche modo, le enumerazioni tendono a non essere gli elementi guida di OO poiché il tipo di entità può essere considerato simile alle informazioni che di solito sono fornite da un enum.
Prendi per es. il campione IceCream
di # 2, l'entità IceCream
anziché i flag hanno una collezione di oggetti Scoop
.
L'approccio meno purista sarebbe che Scoop
avesse una proprietà Flavor
. L'approccio purista sarebbe che Scoop
fosse una classe base astratta per VanillaScoop
, ChocolateScoop
, ... invece classi.
La linea di fondo è questa:
1. Non tutto ciò che è un "tipo di qualcosa" deve essere enum
2. Quando alcuni membri dell'enumerazione non sono un flag valido in alcuni scenari, prendi in considerazione la possibilità di suddividere l'enum in più enumerazioni distinte.
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 proprio estendibile!):
public class User
{
public bool IsAdmin { get; set; }
}
In altre parole - è implicito che User
è un User
, le informazioni aggiuntive sono se è un Admin
.
Se si dispone di più ruoli che non sono esclusivi (ad esempio User
può essere Admin
, Moderator
, VIP
, ... allo stesso tempo), sarebbe un buon momento per usare enif flag o una classe base o un'interfaccia di base.
L'utilizzo di una classe per rappresentare Role
comporta una migliore separazione delle responsabilità in cui una Role
può avere la responsabilità di decidere se può eseguire una determinata azione.
Con un enum avresti bisogno di avere la logica in un posto per tutti i ruoli. Che sconfigge lo scopo di OO e ti riporta indietro all'imperativo.
Immagina che Moderator
abbia diritti di modifica e Admin
abbia entrambi i diritti di modifica ed eliminazione.
Approccio Enum (chiamato Permissions
per non mescolare ruoli e permessi):
[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 è lontano dall'essere perfetto, idealmente, vorrai IAction
in uno schema di 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ò essere un approccio accettabile sebbene (ad esempio, le prestazioni). La decisione qui dipende dai requisiti del sistema modellato.