TL; DR: Обычно плохая идея использовать коллекцию перечислений, так как это часто приводит к плохому дизайну. Коллекция перечислений обычно требует отдельных системных объектов с определенной логикой.
Необходимо различать несколько случаев использования enum. Этот список только в верхней части моей головы, поэтому может быть больше случаев ...
Все примеры написаны на C #, я думаю, у вашего языка будут схожие конструкции, или вы сможете реализовать их самостоятельно.
1. Допустимо только одно значение
В этом случае значения являются исключительными, например,
public enum WorkStates
{
Init,
Pending,
Done
}
Недопустимо иметь какую-то работу, которая одновременно Pending
и Done
. Поэтому только одно из этих значений является действительным. Это хороший пример использования enum.
2. Допустима комбинация значений.
Этот случай также называется flags, C # предоставляет [Flags]
атрибут enum для работы с ними. Идея может быть смоделирована как набор bool
s или bit
s, каждый из которых соответствует одному члену перечисления. Каждый член должен иметь значение силы два. Комбинации могут быть созданы с использованием побитовых операторов:
[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
}
Использование коллекции членов перечисления является излишним в таком случае, поскольку каждый член перечисления представляет только один бит, который установлен или не установлен. Я думаю, что большинство языков поддерживают такие конструкции. В противном случае вы можете создать его (например, использовать bool[]
и адресовать его (1 << (int)YourEnum.SomeMember) - 1
).
а) Все комбинации действительны
Хотя в некоторых простых случаях это нормально, коллекция объектов может оказаться более подходящей, поскольку вам часто требуется дополнительная информация или поведение в зависимости от типа.
[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);
}
}
(примечание: это предполагает, что вы действительно заботитесь только о вкусах мороженого - вам не нужно моделировать мороженое как набор шариков и конуса)
б) Некоторые комбинации значений действительны, а некоторые нет
Это частый сценарий. Часто дело в том, что вы помещаете две разные вещи в одно перечисление. Пример:
[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; }
}
Теперь, несмотря на то, что для s и s вполне допустимо Vehicle
и Building
для Door
s, и для Window
s не очень обычно Building
иметьWheel
с.
В этом случае было бы лучше разбить перечисление на части и / или изменить иерархию объектов, чтобы получить вариант № 1 или № 2а).
Особенности дизайна
Каким-то образом перечисления, как правило, не являются движущими элементами в ОО, поскольку тип сущности может считаться аналогичным информации, которую обычно предоставляет перечисление.
Возьмите, например, IceCream
образец из # 2, у IceCream
объекта вместо флагов будет коллекцияScoop
объектов.
Менее пуристом подход был бы для Scoop
иметь Flavor
собственность. Пуристом подход был бы для Scoop
быть абстрактным базовым классом для VanillaScoop
,ChocolateScoop
, ... классов вместо этого.
Суть в том, что:
1. Не все, что является "типом чего-то", должно быть перечислением
2. Когда какой-либо член перечисления не является допустимым флагом в некотором сценарии, рассмотрите возможность разделения перечисления на несколько различных перечислений.
Теперь для вашего примера (немного изменено):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
Я думаю, что этот точный случай должен быть смоделирован как (примечание: не очень расширяемый!):
public class User
{
public bool IsAdmin { get; set; }
}
Другими словами - подразумевается, что User
это User
, дополнительная информация, является ли онAdmin
.
Если вы иметь несколько ролей , которые не являются взаимоисключающими (например , User
может быть Admin
, Moderator
, VIP
, ... в то же время), что будет время хорошо использовать либо флаги ENUM или Abtract базовый класс или интерфейс.
Использование класса для представления Role
ведет к лучшему разделению обязанностей, когда у него Role
может быть ответственность, чтобы решить, может ли он выполнить данное действие.
С перечислением вам нужно было бы иметь логику в одном месте для всех ролей. Который побеждает цель ОО и возвращает вас к необходимости.
Представьте, что у вас Moderator
есть права на редактирование иAdmin
редактирование, а также права на редактирование и удаление.
Подход Enum (вызывается Permissions
для того, чтобы не смешивать роли и разрешения):
[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;
}
}
Классовый подход (это далеко не идеально, в идеале, вы хотели бы, чтобы IAction
в шаблоне посетителя, но этот пост уже огромен ...):
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));
}
}
Использование перечисления может быть приемлемым подходом (например, производительность). Решение здесь зависит от требований моделируемой системы.