È una buona pratica usare Elenco di enumerazioni?


32

Attualmente sto lavorando su un sistema in cui sono presenti Utenti e ogni utente ha uno o più ruoli. È buona norma utilizzare l'elenco dei valori Enum sull'utente? Non riesco a pensare a niente di meglio, ma questo non mi fa sentire bene.

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

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

6
Per me va bene, sarei interessato a vedere i commenti di chiunque al contrario.
David Scholefield,

9
@MatthewRock è una generalizzazione piuttosto ampia. Elenco <T> è abbastanza comune nel mondo .NET.
Graham,

7
@MatthewRock .NET List è un arraylist, che ha le stesse proprietà per gli algoritmi che menzioni.
così confuso il

18
@MatthewRock - no, stai parlando degli elenchi LINKED, quando la domanda e tutti gli altri parlano dell'interfaccia dell'elenco generico.
Davor Ždralo,

5
Le proprietà automatiche sono una caratteristica distintiva di C # (la sintassi get; set;). Anche la denominazione della classe List.
jaypb,

Risposte:


37

TL; DR: Di solito è una cattiva idea usare una collezione di enumerazioni poiché spesso porta a un cattivo design. Una raccolta di enum di solito richiede entità di sistema distinte con una logica specifica.

È necessario distinguere tra alcuni casi d'uso di enum. Questo elenco è solo in cima alla mia testa, quindi potrebbero esserci più casi ...

Gli esempi sono tutti in C #, immagino che il tuo linguaggio di scelta avrà costrutti simili o sarebbe possibile per te implementarli da solo.

1. È valido solo un valore singolo

In questo caso, i valori sono esclusivi, ad es

public enum WorkStates
{
    Init,
    Pending,
    Done
}

Non è valido avere un lavoro che sia sia Pendinge Done. Pertanto, solo uno di questi valori è valido. Questo è un buon caso d'uso di enum.

2. È valida una combinazione di valori
Questo caso è anche chiamato flag, C # fornisce l' [Flags]attributo enum per lavorare con questi. L'idea può essere modellata come un insieme di bools o bits con ciascuna corrispondente a un membro enum. Ogni membro dovrebbe avere un valore di potenza di due. Le combinazioni possono essere create usando 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'uso di una raccolta di membri enum è un problema eccessivo in tal caso, poiché ciascun membro enum rappresenta solo un bit impostato o non impostato. Immagino che la maggior parte delle lingue supportino costrutti come questo. Altrimenti puoi crearne uno (es. Usalo bool[]e indirizzalo entro (1 << (int)YourEnum.SomeMember) - 1).

a) Tutte le combinazioni sono valide

Anche se questi sono ok in alcuni casi semplici, una raccolta di oggetti può essere più appropriata in quanto spesso sono necessarie ulteriori informazioni o comportamenti 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 davvero solo ai 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 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 per entrambi Vehiclee Buildingper avere Doors e Windows, non è molto normale per Buildings avere Wheels.

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 sul design

In qualche modo, gli enum tendono a non essere gli elementi trainanti in OO poiché il tipo di entità può essere considerato simile alle informazioni che sono di solito fornite da un enum.

Prendi ad esempio il IceCreamcampione dal n. 2, l' IceCreamentità invece delle bandiere avrebbe una raccolta di Scoopoggetti.

L'approccio meno purista sarebbe per Scoopavere una Flavorproprietà. L'approccio purista sarebbe per la Scoopdi essere una classe base astratta per VanillaScoop, ChocolateScoop, ... classi invece.

La linea di fondo è che:
1. Non tutto ciò che è un "tipo di qualcosa" deve essere enum
2. Quando un membro enum non è un flag valido in alcuni scenari, considerare di dividere l'enum in più enum distinti.

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 realmente estensibile!):

public class User
{
    public bool IsAdmin { get; set; }
}

In altre parole - è implicito, che Userè un User, l'informazione aggiuntiva è se è un Admin.

Se si arriva ad avere più ruoli che non sono esclusivi (ad esempio, Userpuò essere Admin, Moderator, VIP, ... allo stesso tempo), che sarebbe un buon momento per utilizzare sia le bandiere enum o una classe base abtract o interfaccia.

L'uso di una classe per rappresentare una Roleporta a una migliore separazione delle responsabilità in cui una Rolepuò avere la responsabilità di decidere se può fare una determinata azione.

Con un enum dovresti avere la logica in un posto per tutti i ruoli. Che sconfigge lo scopo di OO e ti riporta all'imperativo.

Immagina che a Moderatorabbia diritti di modifica e Adminabbia diritti sia di modifica che di eliminazione.

Approccio Enum (chiamato Permissionsper non mescolare ruoli e autorizzazioni):

[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 è tutt'altro che perfetto, idealmente, vorresti il IActionmodello in un 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ò tuttavia essere un approccio accettabile (ad es. Performance). La decisione qui dipende dai requisiti del sistema modellato.


3
Non penso che [Flags]implica che tutte le combinazioni siano valide più di quanto un int implichi che tutti i quattro miliardi di valori di quel tipo siano validi. Significa semplicemente che possono essere combinati all'interno di un singolo campo - qualsiasi restrizione sulle combinazioni appartiene alla logica di livello superiore.
Casuale 832,

Avvertenza: durante l'utilizzo [Flags], è necessario impostare i valori su potenze di due, altrimenti non funzionerà come previsto.
Arturo Torres Sánchez,

@ Random832 Non ho mai avuto intenzione di dirlo, ma ho modificato la risposta - spero che ora sia più chiaro.
Zdeněk Jelínek,

1
@ ArturoTorresSánchez Grazie per l'input, ho corretto la risposta e ho anche aggiunto una nota esplicativa al riguardo.
Zdeněk Jelínek,

Grande spiegazione! In realtà i flag si adattano molto bene ai requisiti di sistema, tuttavia sarà più facile usare gli Hashset a causa dell'implementazione del servizio esistente.
Dexie,

79

Perché non usare Set? Se si utilizza Elenco:

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

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


35
In realtà in Java EnumSetsono implementati come campi di bit.
biziclop,

4
Domanda SO correlati su HashSet<MyEnum>bandiere contro enumerazioni: stackoverflow.com/q/9077487/87698
Heinzi

3
.NET ha FlagsAttribute, che consente di utilizzare operatori bit per concatenare enumerazioni. Sembra simile a Java EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute EDIT: avrei dovuto guardare in basso una risposta! ne ha un buon esempio.
ps2goat,

4

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

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

3
Qualche motivo per cui non stai usando 1?
Robbie Dee,

1
@RobbieDee non ho potuto usare efficacemente 1, 2, 4, 8, ecc. Hai ragione
Rémi

5
Bene, se usi Java, ha già quello EnumSetche lo implementa per enums. Hai già sottratto tutta l'aritmetica booleana, oltre ad alcuni altri metodi per renderlo più facile da usare, oltre a poca memoria e buone prestazioni.
13

2
@ fr13d Sono ac # guy e poiché la domanda non ha tag java, penso che questa risposta si applichi più in generale
Rémi,

1
Bene, @ Rémi. C # fornisce l' [flags]attributo che @ Zdeněk Jelínek esplora nel suo post .
dal

4

È buona norma utilizzare l'elenco dei valori Enum sull'utente?


Risposta breve : Sì


Migliore risposta breve : Sì, l'enum definisce qualcosa nel dominio.


Risposta in fase di progettazione : crea e usa classi, strutture, ecc. Che modellano il dominio in termini di dominio stesso.


Risposta al tempo di programmazione : ecco come codificare gli enum ...


Le domande dedotte :

  • Dovrei usare le stringhe?

    • risposta: no
  • Dovrei usare qualche altra classe?

    • Inoltre, sì. Invece di no.
  • L' Roleelenco enum è sufficiente per l'uso in User?

    • Non ne ho idea. Dipende fortemente da altri dettagli di progettazione non in evidenza.

WTF Bob?

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

    • Un enumè un buon modo per definire "Ruoli" nel tuo modello. Quindi uno Listdei 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. Non è nulla per quanto riguarda il dominio.
  • Qualsiasi classe tu possa progettare per dare il concetto di un Rolepo 'di vita - funzionalità - può fare buon uso Roledell'enum.

    • Ad esempio, anziché sottoclasse per ogni tipo di ruolo, avere una Roleproprietà che indichi quale tipo di ruolo è questa istanza di classe.
    • Un User.Roleselenco è ragionevole quando non abbiamo bisogno o non vogliamo oggetti ruolo istanziati.
    • E quando abbiamo bisogno di creare un'istanza di un ruolo, l'enum è un modo non ambiguo e sicuro per comunicarlo a una RoleFactoryclasse; e, non trascuratamente, per il lettore di codici.

3

Non è qualcosa che ho visto ma non vedo alcun motivo per cui non funzionerebbe. Potresti voler usare un elenco ordinato in modo che difenda contro i duplicati.

Un approccio più comune è un singolo livello utente, ad esempio sistema, amministratore, utente esperto, 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 ricavarne i ruoli se volessi davvero memorizzarli in un singolo campo, ma ciò potrebbe non essere adatto ai gusti di tutti.

Quindi potresti avere:

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

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


2

Usa HashSet in quanto impedisce i duplicati e ha più metodi di confronto

Altrimenti buon uso di Enum

Aggiungi un metodo IsInRole booleano (ruolo R)

e salterei il set

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

1

L'esempio semplicistico che dai va bene, ma in genere è più complicato di così. Ad esempio, se hai intenzione di persistere utenti e ruoli in un database, allora dovresti definire i ruoli in una tabella del database piuttosto che usare enum. Questo ti dà integrità referenziale.


0

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.