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 Pendinget 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 bools ou bits, 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 Vehicleet Buildingpour avoir des Doors et Windows, il n’est pas très courant que Buildings 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' IceCreamexemple n ° 2, l' IceCreamentité à la place des drapeaux aurait une collection d' Scoopobjets.
L'approche moins puriste consisterait Scoopà avoir une Flavorpropriété. L’approche puriste serait que le Scoopsoit 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 Userest 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 , Userpeut ê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 Roleconduit à 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 Moderatora des droits de modification etAdmin modification et de droits de modification et de suppression.
Approche Enum (appelée Permissionspour 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 IActiondans 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é.