È una buona pratica usare List of Enums?

31

Attualmente sto lavorando a un sistema in cui sono presenti utenti e ogni utente ha uno o più ruoli. È una buona pratica usare i valori Elenco di Enum sull'utente? Non riesco a pensare a qualcosa di meglio, ma questo non mi sembra giusto.

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

class User{
   ...
   List<Role> Roles {get;set;}
}
    
posta Dexie 21.01.2016 - 09:54
fonte

8 risposte

79

Perché non usare Set? Se si utilizza Elenco:

  1. È facile aggiungere due volte lo stesso ruolo
  2. Il confronto ingiusto degli elenchi non funzionerà correttamente qui: [Utente, Amministratore] non è uguale a [Amministratore, Utente]
  3. Operazioni complesse come intersect e merge non sono semplici da implementare

Se sei preoccupato per le prestazioni, allora, ad esempio in Java, c'è EnumSet che viene implementato come array a lunghezza fissa di booleani (l'elemento booleano risponde alla domanda se il valore Enum sia presente in questo set o meno). Ad esempio, EnumSet<Role> . Inoltre, consulta EnumMap . Sospetto che C # abbia qualcosa di simile.

    
risposta data 21.01.2016 - 12:08
fonte
36

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.

    
risposta data 21.01.2016 - 13:36
fonte
4

Scrivi il tuo enum in modo da poterli combinare. Usando l'esponenziale di base 2 puoi combinarli tutti in un enum, avere 1 proprietà e controllarli. Il tuo enum dovrebbe essere lile questo

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
    
risposta data 21.01.2016 - 13:17
fonte
4

Is it a good practice to use List of Enum values on User?

Risposta breve : sì

Risposta breve migliore : Sì, l'enum definisce qualcosa nel dominio.

Risposta in fase di progettazione : crea e utilizza classi, strutture e così via che modellano il dominio in termini di dominio stesso.

Risposta per il tempo di codifica : ecco come enumerare le enumerazioni del codice ...

Le domande dedotte :

  • Devo usare le stringhe?

    • risposta: No
  • Dovrei usare qualche altra classe?

    • Oltre a, sì. Invece di, no.
  • L'elenco enum Role è sufficiente per l'uso in User ?

    • Non ne ho idea. Dipende molto da altri dettagli di design non evidenti.

WTF Bob?

  • Questa è una domanda di progettazione incorniciata nei tecnicismi del linguaggio di programmazione.

    • Un enum è un buon modo per definire "ruoli" nel tuo modello. Quindi un List di ruoli è una buona cosa.
  • enum è di gran lunga superiore alle stringhe.

    • Role è una dichiarazione di TUTTI i ruoli esistenti nel dominio.
    • La stringa "Admin" è una stringa con le lettere "A" "d" "m" "i" "n" , in questo ordine. È nulla per quanto riguarda il dominio.
  • Qualsiasi classe che potresti progettare per dare il concetto di Role di alcune funzionalità vitali - può fare buon uso del Role enum.

    • Ad esempio anziché sottoclasse per ogni tipo di ruolo, avere una proprietà Role che indica quale tipo di ruolo è questa istanza di classe.
    • Una lista User.Roles è ragionevole quando non abbiamo bisogno, o non vogliamo, oggetti di ruolo istanziati.
    • E quando abbiamo bisogno di creare un'istanza di un ruolo, l'enum è un modo non ambiguo e sicuro per comunicare a una classe RoleFactory ; e, non in modo insignificante, al lettore di codice.
risposta data 21.01.2016 - 20:00
fonte
3

Non è qualcosa che ho visto, ma non vedo ragioni per cui non funzioni. Potresti voler usare l'elenco ordinato in modo da difendere comunque i duplicati.

Un approccio più comune è un singolo livello utente, ad es. sistema, amministratore, utente avanzato, utente, ecc. Il sistema può fare tutto, amministrare la maggior parte delle cose e così via.

Se dovessi impostare i valori dei ruoli come poteri di due, potresti memorizzare il ruolo come int e derivare i ruoli se davvero volessi archiviarli in un singolo campo, ma ciò potrebbe non essere per tutti i gusti.

Quindi potresti avere:

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

Quindi, se un utente aveva un valore di ruolo di 6, avrebbe i ruoli di Front office e Warehouse.

    
risposta data 21.01.2016 - 13:22
fonte
2

Utilizza HashSet in quanto impedisce duplicati e ha più confronto metodi

Altrimenti buon uso di Enum

Aggiungi un metodo Booleano IsInRole (ruolo R)

e salterò il set

List<Role> roles = new List<Role>();
Public List<Role> Roles { get {return roles;} }
    
risposta data 21.01.2016 - 13:33
fonte
1

L'esempio semplicistico che date è buono, ma in genere è più complicato di così. Ad esempio, se si intende mantenere gli utenti e i ruoli in un database, è necessario definire i ruoli in una tabella di database anziché utilizzare le enumerazioni. Questo ti dà integrità referenziale.

    
risposta data 21.01.2016 - 13:26
fonte
0

Se un sistema - potrebbe essere un software, un sistema operativo o un'organizzazione - si occupa di ruoli e permessi, arriva un momento in cui è utile aggiungere ruoli e gestire le autorizzazioni per loro.
Se questo può essere archiviato modificando il codice sorgente, potrebbe essere ok, attenersi alle enumerazioni. Ma a un certo punto l'utente del sistema vuole essere in grado di gestire ruoli e permessi. Le enumerazioni diventano inutilizzabili.
Invece vorrai avere una struttura dati configurabile. cioè una classe UserRole che detiene anche una serie di permessi assegnati al ruolo.
Una classe utente avrebbe una serie di ruoli. Se è necessario verificare l'autorizzazione dell'utente, viene creato e verificato un set di unione di tutte le autorizzazioni dei ruoli se contiene l'autorizzazione in questione.

    
risposta data 22.01.2016 - 15:18
fonte

Leggi altre domande sui tag