TL; DR: Зазвичай це погана ідея використовувати колекцію перерахунків, оскільки це часто призводить до поганого дизайну. Збірка перерахунків зазвичай вимагає виділення різних системних об'єктів із певною логікою.
Слід розрізняти кілька випадків використання перерахунків. Цей список лише у мене в голові, тому випадків може бути більше ...
Усі приклади є у C #, я думаю, що ваша мова вибору матиме подібні конструкції, або ви могли б їх реалізувати самостійно.
1. Дійсно лише одне значення
У цьому випадку значення є виключними, наприклад
public enum WorkStates
{
Init,
Pending,
Done
}
Недійсно мати якусь роботу, яка є Pending
і Done
. Тому справедливим є лише одне з цих значень. Це хороший випадок використання перерахунків.
2. Поєднання значень є дійсним
Цей випадок також називається прапорами, C # забезпечує [Flags]
атрибут enum для роботи з ними. Ідея може бути змодельована як набір bool
s або bit
s з кожним відповідним одному члену enum. Кожен член повинен мати значення сили два. Комбінації можна створити за допомогою побітових операторів:
[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
}
Використання колекції членів enum є надмірним у такому випадку, оскільки кожен член enum представляє лише один біт, який встановлений або не встановлений. Я думаю, що більшість мов підтримують такі конструкції. В іншому випадку ви можете створити його (наприклад, використовувати 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; }
}
Тепер, хоча це повністю справедливо і для обох, Vehicle
і Building
для Door
s і Window
s, це не дуже звично для Building
s мати Wheel
s.
У цьому випадку було б краще розбити перерахунок на частини та / або змінити ієрархію об’єктів, щоб досягти будь-якого випадку №1 або # 2а).
Дизайнерські міркування
Так чи інакше, перерахунки, як правило, не є рушійними елементами OO, оскільки тип сутності може вважатися подібним до інформації, яку зазвичай надає enum.
Візьмемо, наприклад, IceCream
зразок №2, IceCream
сутність замість прапорів має колекцію Scoop
об'єктів.
Менш пуристичним підходом було б Scoop
мати Flavor
власність. Пуристом підхід був би для Scoop
бути абстрактним базовим класом для VanillaScoop
, ChocolateScoop
, ... класів замість цього.
Суть полягає в тому, що:
1. Не все, що є "типом чогось", повинно бути перерахованим
2. Коли якийсь член enum не є дійсним прапором у певному сценарії, розгляньте про поділ enum на кілька різних перерахунків.
Тепер для вашого прикладу (трохи змінено):
public enum Role
{
User,
Admin
}
public class User
{
public List<Role> Roles { get; set; }
}
Я думаю, що саме цей випадок слід моделювати як (зауважте: не дуже розширюваний!):
public class User
{
public bool IsAdmin { get; set; }
}
Іншими словами - мається на увазі, що User
є a User
, додатковою інформацією є те, чи є він Admin
.
Якщо ви мати кілька ролей , які не є взаємовиключними (наприклад , User
може бути Admin
, Moderator
, VIP
, ... в той же час), що буде час добре використовувати або прапори ENUM або Abtract базовий клас або інтерфейс.
Використання класу для представлення Role
призводить до кращого розподілу обов'язків, коли особа Role
може нести відповідальність вирішувати, чи може він виконати певну дію.
З перерахуванням вам потрібно буде мати логіку в одному місці для всіх ролей. Що перемагає мету ОО і повертає вас до імперативу.
Уявіть, що a 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));
}
}
Використання перерахунку може бути прийнятним підходом (наприклад, ефективність). Рішення тут залежить від вимог модельованої системи.