TL; DR: C'est généralement une mauvaise idée d'utiliser une collection d'énums car cela conduit souvent à une mauvaise conception. Un ensemble d'énums appelle généralement des entités système distinctes avec une logique spécifique.
Il est nécessaire de distinguer quelques cas d'utilisation de enum. Cette liste est seulement de tête, donc il pourrait y avoir plus de cas ...
Les exemples sont tous en C #, je suppose que votre langage de choix aura des constructions similaires ou il vous serait possible de les implémenter vous-même.
1. Seule la valeur unique est valide
Dans ce cas, les valeurs sont exclusives, par exemple
public enum WorkStates
{
Init,
Pending,
Done
}
Il est invalide d'avoir un travail qui est à la fois Pending
et Done
. Par conséquent, une seule de ces valeurs est valide. C’est un bon cas d’utilisation d’enum.
2. Une combinaison de valeurs est valide
Ce cas est également appelé flags, C # fournit un [Flags]
attribut enum pour travailler avec ceux-ci. L'idée peut être modélisée comme un ensemble de bool
s ou bit
s, chacun correspondant à un membre enum. Chaque membre devrait avoir une valeur de pouvoir de deux. Les combinaisons peuvent être créées à l'aide d'opérateurs au niveau du 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'utilisation d'une collection de membres enum est une surcharge, dans la mesure où chaque membre enum ne représente qu'un bit défini ou non défini. Je suppose que la plupart des langues supportent des constructions comme celle-ci. Sinon, vous pouvez en créer un (par exemple, utilisez bool[]
et adressez-le par (1 << (int)YourEnum.SomeMember) - 1
).
a) Toutes les combinaisons sont valables
Bien que cela soit correct dans certains cas simples, une collection d'objets peut être plus appropriée car vous avez souvent besoin d'informations supplémentaires ou d'un comportement basé sur le type.
[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);
}
}
(note: cela suppose que vous ne vous souciez que des saveurs de la glace - vous n'avez pas besoin de la modeler comme une collection de pelles et d'un cornet)
b) Certaines combinaisons de valeurs sont valides et d’autres non
C'est un scénario fréquent. Il arrive souvent que vous mettiez deux choses différentes dans une seule énumération. Exemple:
[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; }
}
Maintenant, bien que ce soit tout à fait valable pour les deux Vehicle
et Building
pour avoir des Door
s et Window
s, il n’est pas très courant que Building
s ait Wheel
.
Dans ce cas, il serait préférable de diviser l'énum en parties et / ou de modifier la hiérarchie des objets afin d'obtenir le cas n ° 1 ou n ° 2a).
Considérations sur la conception
D'une manière ou d'une autre, les enums ont tendance à ne pas être les éléments moteurs de OO car le type d'entité peut être considéré comme similaire aux informations habituellement fournies par une enum.
Prenons l'exemple de l' IceCream
exemple n ° 2, l' IceCream
entité à la place des drapeaux aurait une collection d' Scoop
objets.
L'approche moins puriste consisterait Scoop
à avoir une Flavor
propriété. L’approche puriste serait que le Scoop
soit une classe de base abstraite pourVanillaScoop
, ChocolateScoop
... au lieu des cours.
L'essentiel est que:
essentiel 1. Tout ce qui est un "type de quelque chose" ne doit pas forcément être enum
2. Si un membre enum n'est pas un indicateur valide dans certains scénarios, envisagez de fractionner l'énum en plusieurs enum distincts.
Maintenant, pour votre exemple (légèrement modifié):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
Je pense que ce cas précis devrait être modélisé comme (note: pas vraiment extensible!):
public class User
{
public bool IsAdmin { get; set; }
}
En d'autres termes - il est implicite que le User
est unUser
, l'info supplémentaire est de savoir s'il est un Admin
.
Si vous arrivez à avoir des rôles multiples qui ne sont pas exclusifs (par exemple , User
peut être Admin
, Moderator
,VIP
... en même temps), ce serait un bon moment pour utiliser des drapeaux ENUM ou une classe de base abtract ou interface.
L’utilisation d’une classe pour représenter un Role
conduit à une meilleure séparation des responsabilitésRole
peut avoir la responsabilité de décider s'il peut ou non faire une action donnée.
Avec un enum, vous auriez besoin de la logique en un seul endroit pour tous les rôles. Ce qui défait le but de OO et vous ramène à l'impératif.
Imaginez qu’un Moderator
a des droits de modification etAdmin
modification et de droits de modification et de suppression.
Approche Enum (appelée Permissions
pour ne pas mélanger les rôles et les autorisations):
[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;
}
}
Approche de classe (c'est loin d'être parfait, idéalement, vous voudriez le IAction
dans un modèle de visiteur mais ce post est déjà énorme ...):
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'utilisation d'un enum peut être une approche acceptable (performances). La décision dépend ici des exigences du système modélisé.