Ist es eine gute Praxis, List of Enums zu verwenden?

32

Ich arbeite derzeit an einem System, auf dem Benutzer vorhanden sind und jeder Benutzer eine oder mehrere Rollen hat. Ist es eine gute Praxis, List of Enum-Werte für Benutzer zu verwenden? Ich kann mir nichts Besseres vorstellen, aber das fühlt sich nicht gut an.

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

class User{
   ...
   List<Role> Roles {get;set;}
}
Dexie
quelle
6
Sieht für mich gut aus, ich würde mich über gegenteilige Kommentare anderer freuen.
David Scholefield
9
@MatthewRock das ist eine ziemlich umfassende Verallgemeinerung. Liste <T> ist in der .NET-Welt weit verbreitet.
Graham
7
@MatthewRock .NET List ist eine Arrayliste, die die gleichen Eigenschaften für die von Ihnen erwähnten Algorithmen aufweist.
so verwirrt
18
@MatthewRock - nein, Sie sprechen von VERKNÜPFTEN Listen, wenn die Frage und alle anderen von einer allgemeinen Listenschnittstelle sprechen.
Davor Ždralo
5
Auto-Eigenschaften sind ein charakteristisches Merkmal von C # (get; set; syntax). Auch die Listenklassenbenennung.
Jaypb

Antworten:

37

TL; DR: Es ist normalerweise eine schlechte Idee, eine Sammlung von Aufzählungen zu verwenden, da dies oft zu einem schlechten Design führt. Eine Auflistung von Aufzählungen erfordert normalerweise unterschiedliche Systementitäten mit einer bestimmten Logik.

Es ist notwendig, zwischen einigen Anwendungsfällen von Enum zu unterscheiden. Diese Liste ist nur von höchster Stelle, daher könnte es weitere Fälle geben ...

Die Beispiele sind alle in C #, ich nehme an, Ihre bevorzugte Sprache wird ähnliche Konstrukte haben, oder Sie könnten sie selbst implementieren.

1. Es ist nur ein einziger Wert gültig

In diesem Fall sind die Werte exklusiv, z

public enum WorkStates
{
    Init,
    Pending,
    Done
}

Es ist ungültig, eine Arbeit zu haben, die sowohl Pendingals auch ist Done. Daher ist nur einer dieser Werte gültig. Dies ist ein guter Anwendungsfall für Enum.

2. Eine Kombination von Werten ist gültig.
Dieser Fall wird auch als Flags bezeichnet. C # bietet [Flags]Enum-Attribute für die Arbeit mit diesen. Die Idee kann als eine Menge von bools oder bits modelliert werden, von denen jedes einem Aufzählungsmitglied entspricht. Jedes Mitglied sollte einen Potenzwert von zwei haben. Kombinationen können mit bitweisen Operatoren erstellt werden:

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

Das Verwenden einer Auflistung von Aufzählungselementen ist in einem solchen Fall ein Overkill, dass jedes Aufzählungselement nur ein Bit darstellt, das entweder gesetzt oder nicht gesetzt ist. Ich denke, die meisten Sprachen unterstützen solche Konstrukte. Andernfalls können Sie eine erstellen (z. B. verwenden bool[]und adressieren (1 << (int)YourEnum.SomeMember) - 1).

a) Alle Kombinationen sind gültig

Obwohl dies in einigen einfachen Fällen in Ordnung ist, ist eine Sammlung von Objekten möglicherweise geeigneter, da Sie häufig zusätzliche Informationen oder Verhaltensweisen benötigen, die auf dem Typ basieren.

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

(Hinweis: Dies setzt voraus, dass Sie sich nur wirklich für die Aromen des Eises interessieren - dass Sie das Eis nicht als eine Sammlung von Kugeln und Kegeln modellieren müssen.)

b) Einige Wertekombinationen sind gültig, andere nicht

Dies ist ein häufiges Szenario. Der Fall kann oft sein, dass Sie zwei verschiedene Dinge in eine Aufzählung setzen. Beispiel:

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

Jetzt , während es für beide vollständig gültig ist Vehicleund Buildinghaben Doors und Windows, es ist nicht sehr üblich , Buildings haben Wheels.

In diesem Fall ist es besser, die Aufzählung in Teile aufzuteilen und / oder die Objekthierarchie zu ändern, um entweder Fall 1 oder 2a zu erreichen.

Überlegungen zum Entwurf

Irgendwie sind die Aufzählungen nicht die treibenden Elemente in OO, da der Typ einer Entität als ähnlich zu den Informationen angesehen werden kann, die normalerweise von einer Aufzählung bereitgestellt werden.

Nehmen Sie zB die IceCreamStichprobe aus # 2, die IceCreamEntität würde anstelle von Flags eine Sammlung von ScoopObjekten haben.

Der weniger puristische Ansatz wäre, Scoopeine FlavorImmobilie zu haben . Der puristische Ansatz wäre für die Scoopeine abstrakte Basisklasse für sein VanillaScoop, ChocolateScoopstattdessen ... Klassen.

Die Quintessenz lautet:
1. Nicht alles, was ein "Typ von etwas" ist, muss eine Aufzählung sein.
2. Wenn ein Aufzählungsmitglied in einem bestimmten Szenario kein gültiges Flag ist, sollten Sie die Aufzählung in mehrere verschiedene Aufzählungen aufteilen.

Nun zu deinem Beispiel (leicht verändert):

public enum Role
{
    User,
    Admin
}

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

Ich denke, dieser genaue Fall sollte modelliert werden als (Anmerkung: nicht wirklich erweiterbar!):

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

Mit anderen Worten - es ist implizit, dass das ein Userist User, die zusätzliche Information ist, ob er ein ist Admin.

Wenn Sie mehrere Rollen bekommen haben , die nicht exklusiv sind (zB Userkann sein Admin, Moderator, VIP, ... zur gleichen Zeit), das wäre ein guter Zeitpunkt , um entweder Fahnen zu verwenden Enum oder eine abtract Basisklasse oder Schnittstelle.

Die Verwendung einer Klasse zur Darstellung von a Roleführt zu einer besseren Aufteilung von Verantwortlichkeiten, wenn a Roledie Verantwortung haben kann, zu entscheiden, ob es eine bestimmte Aktion ausführen kann.

Mit einer Aufzählung müssten Sie die Logik für alle Rollen an einem Ort haben. Was den Zweck von OO zunichte macht und dich zum Imperativ zurückbringt.

Stellen Sie sich vor, dass a ModeratorBearbeitungsrechte hat und Adminsowohl über Bearbeitungs- als auch über Löschrechte verfügt.

Enum-Ansatz (aufgerufen, Permissionsum Rollen und Berechtigungen nicht zu mischen):

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

Klasse Ansatz (dies ist alles IActionandere als perfekt, im Idealfall möchten Sie die in einem Besuchermuster, aber dieser Beitrag ist bereits enorm ...):

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

Die Verwendung einer Aufzählung kann jedoch ein akzeptabler Ansatz sein (z. B. Leistung). Die Entscheidung hier hängt von den Anforderungen des modellierten Systems ab.

Zdeněk Jelínek
quelle
3
Ich denke nicht, dass [Flags]impliziert , dass alle Kombinationen mehr gültig sind, als ein Int impliziert, dass alle vier Milliarden Werte dieses Typs gültig sind. Es bedeutet einfach, sie können in einem einzigen Feld kombiniert werden - irgendwelche Beschränkungen für Kombinationen gehören zu höherer Ebene Logik.
Random832
Warnung: Wenn Sie verwenden [Flags], sollten Sie die Werte auf Zweierpotenzen setzen, da dies sonst nicht wie erwartet funktioniert.
Arturo Torres Sánchez
@ Random832 Ich hatte nie die Absicht, das zu sagen, aber ich habe die Antwort bearbeitet - hoffe, es ist jetzt klarer.
Zdeněk Jelínek
1
@ ArturoTorresSánchez Vielen Dank für die Eingabe, ich habe die Antwort behoben und auch eine Erläuterung dazu hinzugefügt.
Zdeněk Jelínek
Tolle Erklärung! Tatsächlich passen Flags sehr gut zu den Systemanforderungen, jedoch wird es aufgrund der bestehenden Service-Implementierung einfacher sein, HashSets zu verwenden.
Dexie
79

Warum nicht Set verwenden? Wenn Sie Liste verwenden:

  1. Es ist einfach, dieselbe Rolle zweimal hinzuzufügen
  2. Naiver Listenvergleich funktioniert hier nicht richtig: [User, Admin] ist nicht dasselbe wie [Admin, User]
  3. Komplexe Operationen wie Verschneiden und Zusammenführen sind nicht einfach zu implementieren

Wenn Sie sich Gedanken über die Leistung machen, gibt es zum Beispiel in Java ein EnumSetArray mit Booleschen Werten fester Länge (das i'te Boolesche Element beantwortet die Frage, ob der i'te Enum-Wert in dieser Menge vorhanden ist oder nicht). Zum Beispiel EnumSet<Role>. Siehe auch EnumMap. Ich vermute, dass C # etwas ähnliches hat.

Gudok
quelle
35
Tatsächlich sind in Java EnumSets als Bitfelder implementiert.
biziclop
4
Verwandte SO Frage zu HashSet<MyEnum>vs. Flags Aufzählungen: stackoverflow.com/q/9077487/87698
Heinzi
3
.NET hat FlagsAttribute, mit dem Sie bitweise Operatoren verwenden können, um Aufzählungen zu verketten. Klingt ähnlich wie Java EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute BEARBEITEN : Ich hätte eine Antwort nach unten schauen sollen! es hat ein gutes Beispiel dafür.
ps2goat
4

Schreiben Sie Ihre Aufzählung, damit Sie sie kombinieren können. Mit dem Exponential zur Basis 2 können Sie alle in einer Aufzählung kombinieren, 1 Eigenschaft haben und nach ihnen suchen. Ihre Aufzählung sollte dies lile sein

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}
Rémi
quelle
3
Gibt es einen Grund, warum Sie nicht 1 verwenden?
Robbie Dee
1
@RobbieDee nicht effektiv ich hätte 1, 2, 4, 8, etc. Sie haben Recht
Rémi
5
Nun, wenn Sie Java verwenden, hat es bereits EnumSetwelche implementiert dies für enums. Sie haben alle Booleschen Arithmetiken bereits abstrahiert und einige andere Methoden, um die Verwendung zu vereinfachen, sowie einen kleinen Speicher und eine gute Leistung.
Freitag,
2
@ Fr13d Ich bin ac # guy und da die Frage kein Java-Tag hat, denke ich, dass diese Antwort mehr auf freiem Fuß gilt
Rémi
1
Richtig, @ Rémi. C # liefert das [flags]Attribut, das @ Zdeněk Jelínek in seinem Beitrag untersucht .
Freitag,
4

Ist es eine gute Praxis, List of Enum-Werte für Benutzer zu verwenden?


Kurze Antwort : Ja


Besser kurze Antwort : Ja, die Aufzählung definiert etwas in der Domain.


Antwort zur Entwurfszeit : Erstellen und verwenden Sie Klassen, Strukturen usw., die die Domäne in Bezug auf die Domäne selbst modellieren.


Antwort zur Codierungszeit : So codieren Sie Sling-Enums ...


Die gefolgerten Fragen :

  • Soll ich Zeichenfolgen verwenden?

    • antwort: nein
  • Soll ich eine andere Klasse benutzen?

    • Außerdem ja. Stattdessen nein.
  • Ist die RoleAufzählungsliste , die zur Verwendung in User?

    • Ich habe keine Ahnung. Es hängt in hohem Maße von anderen Designdetails ab, die nicht belegt sind.

WTF Bob?

  • Dies ist eine Entwurfsfrage, die in Programmiersprachentechniken eingebettet ist.

    • Dies enumist ein guter Weg, um "Rollen" in Ihrem Modell zu definieren. Ein ListRollenwechsel ist also eine gute Sache.
  • enum ist Saiten weit überlegen.

    • Role ist eine Deklaration ALLER Rollen, die in der Domäne vorhanden sind.
    • Die Zeichenfolge "Admin" ist eine Zeichenfolge mit den Buchstaben "A" "d" "m" "i" "n"in dieser Reihenfolge. Es ist nichts , was die Domain betrifft.
  • Alle Klassen, die Sie entwerfen, um das Konzept einer Rolebestimmten Lebensfunktionalität zu vermitteln, können die RoleAufzählung gut nutzen .

    • Verfügen Sie beispielsweise nicht über eine Unterklasse für jede Art von Rolle, sondern über eine RoleEigenschaft, die angibt, um welche Art von Rolle es sich bei dieser Klasseninstanz handelt.
    • Eine User.RolesListe ist sinnvoll, wenn wir keine instanziierten Rollenobjekte benötigen oder wollen.
    • Und wenn wir eine Rolle instanziieren müssen, ist die Aufzählung eine eindeutige, typsichere Möglichkeit, dies einer RoleFactoryKlasse mitzuteilen . und nicht unwesentlich für den Codeleser.
Radarbob
quelle
3

Das habe ich nicht gesehen, aber ich sehe keinen Grund, warum es nicht funktionieren würde. Vielleicht möchten Sie eine sortierte Liste verwenden, um sich jedoch gegen Dupes zu verteidigen.

Ein üblicherer Ansatz ist eine einzelne Benutzerebene, z. B. System, Administrator, Hauptbenutzer, Benutzer usw. Das System kann alles ausführen, die meisten Dinge verwalten und so weiter.

Wenn Sie die Werte von Rollen als Zweierpotenzen festlegen, können Sie die Rolle als Int speichern und die Rollen ableiten, wenn Sie sie wirklich in einem einzelnen Feld speichern möchten, aber dies ist möglicherweise nicht für jeden Geschmack geeignet.

So könnten Sie haben:

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

Wenn ein Benutzer also einen Rollenwert von 6 hat, hat er die Rollen "Front Office" und "Warehouse".

Robbie Dee
quelle
2

Verwenden Sie HashSet, da es Duplikate verhindert und über mehr Vergleichsmethoden verfügt

Ansonsten gute Verwendung von Enum

Hinzufügen einer Methode Boolean IsInRole (Role R)

und ich würde das Set überspringen

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

Das vereinfachte Beispiel, das Sie geben, ist in Ordnung, aber in der Regel ist es komplizierter. Wenn Sie beispielsweise die Benutzer und Rollen in einer Datenbank beibehalten möchten, sollten Sie die Rollen in einer Datenbanktabelle definieren, anstatt Aufzählungen zu verwenden. Das gibt Ihnen referentielle Integrität.

Mohair
quelle
0

Wenn ein System - sei es eine Software, ein Betriebssystem oder eine Organisation - Rollen und Berechtigungen behandelt, kann es sinnvoll sein, Rollen hinzuzufügen und Berechtigungen für diese zu verwalten.
Wenn dies durch Ändern des Quellcodes archiviert werden kann, kann es in Ordnung sein, sich an Aufzählungen zu halten. Aber irgendwann möchte der Benutzer des Systems in der Lage sein, Rollen und Berechtigungen zu verwalten. Dann werden Aufzählungen unbrauchbar.
Stattdessen möchten Sie eine konfigurierbare Datenstruktur haben. dh eine UserRole-Klasse, die auch eine Reihe von Berechtigungen enthält, die der Rolle zugewiesen sind.
Eine Benutzerklasse würde eine Reihe von Rollen haben. Wenn die Berechtigung des Benutzers überprüft werden muss, wird ein Zusammenfassungssatz aller Rollenberechtigungen erstellt und überprüft, ob die betreffende Berechtigung enthalten ist.

VikingoS sagt Reinstate Monica
quelle