web-dev-qa-db-fra.com

Est-ce une bonne pratique d'utiliser la liste des énumérations?

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 la liste des valeurs Enum sur l'utilisateur? Je ne vois rien de mieux, mais cela ne me semble pas correct.

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

class User{
   ...
   List<Role> Roles {get;set;}
}
32
Dexie

TL; DR: C'est généralement une mauvaise idée d'utiliser une collection d'énumérations car cela conduit souvent à une mauvaise conception. Une collection d'énumérations 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 d'énumération. Cette liste n'est que du haut de ma 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 une valeur unique est valide

Dans ce cas, les valeurs sont exclusives, par ex.

public enum WorkStates
{
    Init,
    Pending,
    Done
}

Il n'est pas valide d'avoir du travail à la fois Pending et Done. Par conséquent, une seule de ces valeurs est valide. Il s'agit d'un bon cas d'utilisation d'énumération.

2. Une combinaison de valeurs est valide
Ce cas est également appelé flags, C # fournit [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 doit avoir une valeur de puissance 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 exagération dans un tel cas, car chaque membre enum ne représente qu'un bit qui est défini ou non. Je suppose que la plupart des langues prennent en charge des constructions comme celle-ci. Sinon, vous pouvez en créer un (par exemple, utilisez bool[] et répondez par (1 << (int)YourEnum.SomeMember) - 1).

a) Toutes les combinaisons sont valides

Bien que ceux-ci soient corrects 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);
    }
}

(Remarque: cela suppose que vous ne vous souciez vraiment que des saveurs de la glace - que vous n'avez pas besoin de modéliser la glace comme une collection de cuillères et d'un cône)

b) Certaines combinaisons de valeurs sont valides et d'autres non

Il s'agit d'un scénario fréquent. Le cas peut souvent être que vous mettez deux choses différentes en 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, même s'il est tout à fait valide que Vehicle et Building aient Doors et Windows, il n'est pas très courant que Buildings ait Wheels.

Dans ce cas, il serait préférable de diviser l'énumération en parties et/ou de modifier la hiérarchie des objets afin de réaliser le cas # 1 ou # 2a).

Considérations de conception

D'une manière ou d'une autre, les énumérations ne sont généralement pas les éléments moteurs de OO puisque le type d'entité peut être considéré comme similaire aux informations généralement fournies par une énumération.

Prenez par exemple l'exemple IceCream de # 2, l'entité IceCream aurait à la place des drapeaux une collection d'objets Scoop.

L'approche la moins puriste consisterait à ce que Scoop ait une propriété Flavor. L'approche puriste consisterait à ce que Scoop soit une classe de base abstraite pour les classes VanillaScoop, ChocolateScoop, ... à la place.

L'essentiel est que:
1. Tout ce qui est un "type de quelque chose" n'a pas besoin d'être énuméré
2. Lorsqu'un membre d'énumération n'est pas un indicateur valide dans certains scénarios, envisagez de diviser l'énumération en plusieurs énumérations distinctes.

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 un User, l'info supplémentaire est de savoir s'il est un Admin.

Si vous avez plusieurs rôles non 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 ou une interface de base abtract.

L'utilisation d'une classe pour représenter un Role conduit à une meilleure séparation des responsabilités où un Role peut avoir la responsabilité de décider s'il peut effectuer une action donnée.

Avec une énumération, vous devez avoir la logique en un seul endroit pour tous les rôles. Ce qui va à l'encontre de l'objectif de OO et vous ramène à l'impératif.

Imaginez qu'un Moderator a des droits d'édition et Admin a des droits d'édition et de suppression.

Approche Enum (appelée Permissions pour ne pas mélanger les rôles et les permissions):

[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));
    }
}

Cependant, l'utilisation d'une énumération peut être une approche acceptable (par exemple, les performances). La décision ici dépend des exigences du système modélisé.

38
Zdeněk Jelínek

Pourquoi ne pas utiliser Set? Si vous utilisez List:

  1. Il est facile d'ajouter deux fois le même rôle
  2. La comparaison naïve des listes ne fonctionnera pas correctement ici: [Utilisateur, Admin] n'est pas identique à [Admin, Utilisateur]
  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 y a EnumSet qui est implémenté comme un tableau de longueur fixe de booléens (le ième élément booléen répond à la question de savoir si je '' la valeur Enum est présente ou non dans cet ensemble). Par exemple, EnumSet<Role>. Voir aussi EnumMap . Je soupçonne que C # a quelque chose de similaire.

78
gudok

Est-ce une bonne pratique d'utiliser la liste des valeurs Enum sur l'utilisateur?


Réponse courte: Oui


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


Réponse au moment du design: Créer et utiliser des classes, des structures, etc. qui modélisent le domaine en termes de domaine lui-même.


Réponse en temps de codage: Voici comment coder les énumérations ...


Les questions déduites:

  • Dois-je utiliser des chaînes?

    • réponse: Non.
  • Dois-je utiliser une autre classe?

    • En plus, oui. Au lieu de ça, non.
  • La liste d'énumérations Role est-elle suffisante pour être utilisée dans User?

    • Je n'ai aucune idée. Il dépend fortement d'autres détails de conception qui ne sont pas mis en évidence.

Bob WTF?

  • Il s'agit d'une question de conception qui est encadrée dans les détails techniques du langage de programmation.

    • Un enum est un bon moyen de définir des "rôles" dans votre modèle. Donc, un List de rôles est une bonne chose.
  • enum est de loin supérieur aux chaînes.

    • Role est une déclaration de TOUS les rôles qui existent 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.
  • Toutes les classes que vous pourriez concevoir pour donner au concept de Role une certaine vie - fonctionnalité - peuvent faire bon usage de l'énumération Role.

    • Par exemple, plutôt que de sous-classe pour chaque type de rôle, ayez une propriété Role qui indique le type de rôle de cette instance de classe.
    • UNE User.Roles la liste 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ération est un moyen sûr et non ambigu de communiquer cela à une classe RoleFactory; et, non négligeable, au lecteur de code.
4
radarbob

Écrivez votre énumération afin de pouvoir les combiner. En utilisant l'exponentielle de base 2, vous pouvez les combiner en une seule énumération, avoir 1 propriété et les vérifier. Votre énumération devrait être lil this

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
4
Rémi

Ce n'est pas quelque chose que j'ai vu, mais je ne vois aucune raison pour laquelle cela ne fonctionnerait pas. Vous voudrez peut-être utiliser une liste triée pour qu'elle se défende contre les dupes cependant.

Une approche plus courante est un niveau d'utilisateur unique, par exemple système, administrateur, utilisateur expérimenté, utilisateur, etc. Le système peut tout faire, administrer la plupart des choses, etc.

Si vous deviez définir les valeurs des rôles comme des pouvoirs de deux, vous pourriez stocker le rôle en tant qu'int et dériver les rôles si vous vouliez vraiment les stocker dans un seul champ, mais cela pourrait ne pas être du goût de tout le monde.

Vous pourriez donc avoir:

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

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

3
Robbie Dee

Utilisez HashSet car il empêche les doublons et a plus de méthodes de comparaison

Sinon, bonne utilisation d'Enum

Ajouter une méthode Boolean IsInRole (Role R)

et je sauterais l'ensemble

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

L'exemple simpliste que vous donnez est bien, mais généralement c'est 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.

1
Mohair

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 les autorisations pour eux.
Si cela peut être archivé en modifiant le code source, il peut être correct de s'en tenir aux énumérations. Mais à un moment donné, l'utilisateur du système souhaite pouvoir gérer les rôles et les autorisations. Les énumérations deviennent alors inutilisables.
Au lieu de cela, vous voudrez avoir une structure de données configurable. c'est-à-dire une classe UserRole qui détient également un ensemble d'autorisations affecté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 tous les rôles est créé et vérifié s'il contient l'autorisation en question.

0
vikingosegundo