Est-ce une bonne pratique d'utiliser List of Enums?

32

Je travaille actuellement sur un système où il y a des utilisateurs, et chaque utilisateur a un ou plusieurs rôles. Est-ce une bonne pratique d'utiliser les valeurs List of Enum sur User? Je ne peux penser à rien de mieux, mais cela ne me semble pas très bien.

enum Role{
  Admin = 1,
  User = 2,
}

class User{
   ...
   List<Role> Roles {get;set;}
}
Dexie
la source
6
Cela me semble bien, je serais intéressé de voir les commentaires de quelqu'un d'autre à l'effet contraire.
David Scholefield le
9
@ MatthewRock, c'est une généralisation assez radicale. La liste <T> est assez courante dans le monde .NET.
Graham
7
@MatthewRock .NET List est un arraylist, qui a les mêmes propriétés que les algorithmes mentionnés.
Je suis tellement confus
18
@ MatthewRock - non, vous parlez de listes LINKED, alors que la question, et tout le monde, parle de l'interface générique de liste.
Davor Ždralo
5
Les propriétés automatiques sont une caractéristique distinctive de C # (la syntaxe get, set;). Également la dénomination de classe List.
Jaypb

Réponses:

37

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é.

Zdeněk Jelínek
la source
3
Je ne pense pas que cela [Flags]implique que toutes les combinaisons sont valables, pas plus qu'un int ne suppose que les quatre milliards de valeurs de ce type sont valables. Cela signifie simplement qu'elles peuvent être combinées dans un seul champ - toute restriction sur les combinaisons appartient à la logique de niveau supérieur.
Random832
Attention: lors de l'utilisation [Flags], définissez les valeurs sur deux ou trois, sinon cela ne fonctionnera pas comme prévu.
Commentaires
@ Random832 Je n'ai jamais eu l'intention de le dire, mais j'ai modifié la réponse - j'espère que c'est plus clair maintenant.
Zdeněk Jelínek
1
@ ArturoTorresSánchez Merci pour votre contribution, j'ai corrigé la réponse et ajouté une note explicative à ce sujet.
Zdeněk Jelínek
Grande explication! En fait, les indicateurs correspondent très bien aux exigences du système, mais l'utilisation de HashSets sera facilitée par la mise en œuvre de services existante.
Dexie
79

Pourquoi ne pas utiliser Set? Si vous utilisez la liste:

  1. Il est facile d'ajouter le même rôle deux fois
  2. La comparaison naïve de listes ne fonctionnera pas correctement ici: [User, Admin] n'est pas la même chose que [Admin, User]
  3. Les opérations complexes telles que l'intersection et la fusion ne sont pas simples à mettre en œuvre

Si vous êtes préoccupé par les performances, alors, par exemple en Java, il existe EnumSetun tableau de booléens de longueur fixe (un élément booléen répond à la question de savoir si une valeur Enum est présente dans cet ensemble ou non). Par exemple, EnumSet<Role>. Aussi, voir EnumMap. Je soupçonne que C # a quelque chose de similaire.

Gudok
la source
35
En réalité, dans Java, EnumSetles implémentations sont des champs de bits.
biziclop
4
SO question connexe sur les HashSet<MyEnum>drapeaux enums: stackoverflow.com/q/9077487/87698
Heinzi
3
.NET a FlagsAttribute, qui vous permet d’utiliser des opérateurs au niveau des bits pour concaténer des énumérations. Cela ressemble à Java EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute EDIT: J'aurais dû regarder une réponse! cela en est un bon exemple.
ps2goat
4

Écris ton enum pour pouvoir les combiner. En utilisant la base 2 exponentielle, vous pouvez les combiner tous dans une énumération, avoir une propriété et les vérifier. Votre enum devrait être un peu ça

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
Rémi
la source
3
Une raison pour laquelle vous n'utilisez pas 1?
Robbie Dee
1
@RobbieDee aucun effet j'aurais pu utiliser 1, 2, 4, 8, etc. vous avez raison
Rémi
5
Eh bien, si vous utilisez Java, il existe déjà EnumSetqui implémente cela pour enums. Toute l'arithmétique booléenne est déjà résumée, ainsi que d'autres méthodes facilitant son utilisation, une petite mémoire et de bonnes performances.
13
2
@ fr13d Je suis un gars et comme la question n'a pas de balise java, je pense que cette réponse s'applique plus généralement
Rémi, le
1
Bien, @ Rémi. C # fournit l' [flags]attribut, que @ Zdeněk Jelínek explore dans son message .
13
4

Est-ce une bonne pratique d'utiliser les valeurs List of Enum sur User?


Réponse courte : oui


Meilleure réponse courte : Oui, l'énumération définit quelque chose dans le domaine.


Réponse au moment de la conception : créez et utilisez des classes, des structures, etc. qui modélisent le domaine en termes de domaine lui-même.


Réponse du temps de codage : Voici comment coder des enumères ...


Les questions inférées :

  • Devrais-je utiliser des chaînes?

    • réponse: non
  • Devrais-je utiliser une autre classe?

    • En plus de, oui. Au lieu de, non.
  • La Roleliste enum est-elle suffisante pour être utilisée User?

    • Je n'ai aucune idée. Il est fortement dépendant d'autres détails de conception non mis en évidence.

WTF Bob?

  • C'est une question de conception qui est encadrée dans les aspects techniques du langage de programmation.

    • Un enumest un bon moyen de définir des "rôles" dans votre modèle. Alors, un Listrôle est une bonne chose.
  • enum est de loin supérieur aux cordes.

    • Role est une déclaration de TOUS les rôles existants dans le domaine.
    • La chaîne "Admin" est une chaîne avec les lettres "A" "d" "m" "i" "n", dans cet ordre. Ce n'est rien en ce qui concerne le domaine.
  • Toute classe que vous pourriez concevoir pour donner le concept de Rolevie - fonctionnalité - peut faire bon usage de l’ Roleénumération.

    • Par exemple, plutôt que de sous-classe pour chaque type de rôle, ayez une Rolepropriété qui indique le type de rôle de cette instance de classe.
    • Une User.Rolesliste est raisonnable lorsque nous n'avons pas besoin, ou ne voulons pas, d'objets de rôle instanciés.
    • Et lorsque nous avons besoin d'instancier un rôle, l'énum est un moyen non ambigu, de type sûr, de communiquer cela à une RoleFactoryclasse; et non négligeable, au lecteur de code.
radarbob
la source
3

Ce n'est pas quelque chose que j'ai vu mais je ne vois aucune raison pour que cela ne fonctionne pas. Vous voudrez peut-être utiliser la liste triée pour la protéger contre les dupes.

Une approche plus courante est un niveau d'utilisateur unique, par exemple système, administrateur, utilisateur privilégié, utilisateur, etc. Le système peut tout faire, la plupart des tâches administratives, etc.

Si vous définissiez les valeurs de rôles comme des puissances à deux, vous pourriez stocker le rôle en tant qu'int et dériver les rôles si vous souhaitiez vraiment les stocker dans un seul champ, mais cela ne conviendrait peut-être pas à tout le monde.

Donc vous pourriez avoir:

Back office role 1
Front office role 2
Warehouse role 4
Systems role 8

Ainsi, si un utilisateur avait une valeur de rôle de 6, il aurait alors les rôles Front office et Warehouse.

Robbie Dee
la source
2

Utilisez HashSet, car il évite les doublons et a plus de méthodes de comparaison

Sinon bon usage de Enum

Ajouter une méthode Boolean IsInRole (rôle R)

et je sauterais l'ensemble

List<Role> roles = new List<Role>();
Public List<Role> Roles { get {return roles;} }
paparazzo
la source
1

L'exemple simpliste que vous donnez est correct, mais il est généralement plus compliqué que cela. Par exemple, si vous souhaitez conserver les utilisateurs et les rôles dans une base de données, vous devez définir les rôles dans une table de base de données plutôt que d'utiliser des énumérations. Cela vous donne une intégrité référentielle.

Mohair
la source
0

Si un système - qu'il s'agisse d'un logiciel, d'un système d'exploitation ou d'une organisation - traite des rôles et des autorisations, il arrive un moment où il est utile d'ajouter des rôles et de gérer leurs autorisations.
Si cela peut être archivé en modifiant le code source, il serait peut-être correct de s'en tenir à des énumérations. Mais à un moment donné, l'utilisateur du système veut pouvoir gérer les rôles et les autorisations. Que des enums deviennent inutilisables.
Au lieu de cela, vous voudrez avoir une structure de données configurable. c'est-à-dire une classe UserRole qui contient également un ensemble d'autorisations assignées au rôle.
Une classe d'utilisateurs aurait un ensemble de rôles. S'il est nécessaire de vérifier l'autorisation de l'utilisateur, un ensemble d'unions de toutes les autorisations de rôles est créé et vérifié s'il contient l'autorisation en question.

VikingoS dit Réintégrer Monica
la source