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 Pending
als 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 bool
s oder bit
s 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 Vehicle
und Building
haben Door
s und Window
s, es ist nicht sehr üblich , Building
s haben Wheel
s.
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 IceCream
Stichprobe aus # 2, die IceCream
Entität würde anstelle von Flags eine Sammlung von Scoop
Objekten haben.
Der weniger puristische Ansatz wäre, Scoop
eine Flavor
Immobilie zu haben . Der puristische Ansatz wäre für die Scoop
eine abstrakte Basisklasse für sein VanillaScoop
, ChocolateScoop
stattdessen ... 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 User
ist User
, die zusätzliche Information ist, ob er ein ist Admin
.
Wenn Sie mehrere Rollen bekommen haben , die nicht exklusiv sind (zB User
kann 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 Role
führt zu einer besseren Aufteilung von Verantwortlichkeiten, wenn a Role
die 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 Moderator
Bearbeitungsrechte hat und Admin
sowohl über Bearbeitungs- als auch über Löschrechte verfügt.
Enum-Ansatz (aufgerufen, Permissions
um 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 IAction
andere 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.