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.