使用枚举列表是否是一个好习惯?


32

我目前正在一个有用户的系统上工作,每个用户都有一个或多个角色。在User上使用Enum值列表是一种好习惯吗?我想不出更好的办法,但这感觉还不错。

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

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

6
在我看来很好,我很想看到其他人的评论相反。
David Scholefield,

9
@MatthewRock多数民众赞成在一个相当全面的概括。List <T>在.NET世界中很常见。
格雷厄姆

7
@MatthewRock .NET列表是一个数组列表,与您提到的算法具有相同的属性。
我很困惑

18
@MatthewRock-不,您是在谈论链接列表,当问题以及其他所有人都在谈论通用列表接口时。
DavorŽdralo'16

5
自动属性是C#的一项独特功能(get; set;语法)。也是List类的命名。
jaypb

Answers:


37

TL; DR:使用枚举集合通常是一个坏主意,因为它通常会导致不良的设计。枚举的集合通常要求具有特定逻辑的不同系统实体。

有必要区分一些枚举用例。这个清单只是我的头等大事,所以可能会有更多的情况...

这些示例全部使用C#编写,我想您选择的语言将具有类似的构造,或者您可以自己实现。

1.仅单个值有效

在这种情况下,这些值是互斥的,例如

public enum WorkStates
{
    Init,
    Pending,
    Done
}

它是无效的一些工作,既PendingDone。因此,这些值中只有一个有效。这是枚举的一个很好的用例。

2.值的组合是有效的
这种情况也称为标志,C#提供了[Flags]枚举属性来处理这些标志。可以将该想法建模为一组bool或,bit其中每个对应一个枚举成员。每个成员的乘方数值应为2。可以使用按位运算符创建组合:

[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)。

a)所有组合均有效

尽管在某些简单情况下可以使用这些对象,但是对象集合可能更合适,因为您通常需要基于类型的其他信息或行为。

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

(注意:这是假设您只真正关心冰淇淋的口味-不需要将冰淇淋建模为勺子和圆锥的集合)

b)某些值组合是有效的,而某些则无效

这是一个常见的情况。通常情况是,您将两个不同的东西放到一个枚举中。例:

[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,并BuildingDoorS和WindowS,它不是很平常的Buildings到有Wheel秒。

在这种情况下,最好将枚举分解为多个部分和/或修改对象层次结构,以实现情况#1或#2a)。

设计注意事项

不知何故,枚举往往不是OO中的驱动因素,因为可以将实体的类型视为类似于枚举通常提供的信息。

IceCream#2中的样本为例,该IceCream实体将代替标志而具有Scoop对象的集合。

较不纯粹的方法是Scoop拥有Flavor财产。最纯粹的做法是为Scoop成为一个抽象基类VanillaScoopChocolateScoop...类来代替。

底线是:
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可以是AdminModeratorVIP,...在同一时间),这将是一个很好用的时间无论是标志枚举或abtract基类或接口。

使用类来表示Role线索可以更好地划分职责,其中a Role可以负责决定是否可以执行给定的操作。

使用枚举,您需要将所有角色的逻辑放在一个地方。这违背了OO的目的,并使您回到当务之急。

假设a Moderator具有编辑权限,Admin并且同时具有编辑和删除权限。

枚举方法(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));
    }
}

但是,使用枚举可能是可以接受的方法(例如,性能)。这里的决定取决于建模系统的要求。


3
我认为这[Flags]意味着所有组合都是有效的,而int意味着该类型的所有四十亿个值都是有效的。这只是意味着它们可以在单个字段中组合-组合的任何限制都属于更高级别的逻辑。
Random832 '16

警告:使用时[Flags],应将值设置为2的幂,否则将无法正常工作。
Arturo TorresSánchez16年

@ Random832我从来没有这么说过,但我编辑了答案-希望现在更清楚。
兹德涅克耶利内克

1
@ArturoTorresSánchez感谢您的输入,我已经确定了答案,并且还添加了解释性注释。
兹德涅克耶利内克

很好的解释!标记实际上非常适合系统要求,但是由于现有的服务实现,使用HashSet会更容易。
Dexie

79

为什么不使用Set?如果使用列表:

  1. 两次添加相同的角色很容易
  2. 列表的幼稚比较在这里无法正常工作:[用户,管理员]与[管理员,用户]不同
  3. 相交和合并之类的复杂操作并不容易实现

如果您担心性能,那么例如在Java中,可以将EnumSet其实现为固定长度的布尔数组(第i个布尔元素回答是否在该集合中存在第i个Enum值的问题)。例如,EnumSet<Role>。另请参阅EnumMap。我怀疑C#有类似的东西。


35
实际上,在Java中,EnumSet它们是作为位字段实现的。
biziclop,2016年

4
HashSet<MyEnum>
vs.flags

3
.NET具有FlagsAttribute,它使您可以使用按位运算符来连接枚举。听起来类似于Java EnumSetmsdn.microsoft.com/zh-CN/LIBRARY/system.flagsattribute编辑:我应该看不起一个答案!它有一个很好的例子。
ps2goat

4

编写您的枚举,以便可以将它们合并。通过使用以2为底的指数,您可以将它们全部合并为一个枚举,具有1个属性并可以进行检查。你的枚举应该是这样

enum MyEnum
{ 
    FIRST_CHOICE = 2,
    SECOND_CHOICE = 4,
    THIRD_CHOICE = 8
}

3
为什么不使用1?
罗比·迪

1
@RobbieDee没有用途不同,我可以用1,2,4,8,等你说得对
雷米

5
好吧,如果您使用Java,它已经EnumSetenums 实现了此功能。您已经提取了所有布尔运算,还添加了一些使其更易于使用的方法,以及较小的内存和良好的性能。
fr13d

2
@ fr13d我是一个C#的家伙,因为这个问题已经没有java的标签我觉得这个答案更适用于大
雷米

1
对,@Rémi。C#提供了[flags]@ZdeněkJelínek在其帖子中探讨的属性。
fr13d

4

在User上使用Enum值列表是一种好习惯吗?


简短答案:是


更好的简短答案:是的,枚举定义了域中的某些内容。


设计时答案:制作和使用根据领域本身对领域进行建模的类,结构等。


编码时间答案:这是枚举枚举代码的方法...


推断的问题

  • 我应该使用琴弦吗?

    • 答:不可以。
  • 我应该使用其他班级吗?

    • 另外,是的。相反,没有。
  • Role枚举列表足够使用User

    • 我不知道。它高度依赖没有证据的其他设计细节。

WTF鲍勃?

  • 这是编程语言技术中的设计问题。

    • An enum是在模型中定义“角色”的好方法。因此List,角色扮演是一件好事。
  • enum 远胜于弦乐。

    • Role 是域中存在的所有角色的声明。
    • 字符串“ Admin”是按字母"A" "d" "m" "i" "n"顺序排列的字符串。它是什么,只要域而言。
  • 您可以设计用来赋予Role生命概念(功能)的任何类都可以充分利用Role枚举。

    • 例如,而不是为每种角色提供子类,而应具有一个Role属性来指示此类实例是哪种角色。
    • 一个User.Roles列表是合理的,当我们不需要或不想要的,实例化的角色对象。
    • 而且,当我们确实需要实例化角色时,枚举是一种明确的,类型安全的方式来将其传达给RoleFactory类。并且对于代码阅读器而言也并非无关紧要。

3

这不是我见过的东西,但我看不出为什么它不起作用。您可能要使用排序列表,以防止重复。

更为常见的方法是单个用户级别,例如系统,管理员,超级用户,用户等。系统可以执行所有操作,管理大多数事情,等等。

如果要将角色的值设置为2的幂,则可以将角色存储为一个int值,并且如果您确实想将角色存储在单个字段中,则可以派生该角色,但这可能并不符合每个人的喜好。

所以你可能有:

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

因此,如果用户的角色值为6,则他们将具有“前台”和“仓库”角色。


2

使用HashSet,因为它可以防止重复并且具有更多比较方法

否则很好地使用枚举

添加方法Boolean IsInRole(Role R)

我会跳过集

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

1

您提供的简单示例很好,但通常比这复杂。例如,如果要将用户和角色保留在数据库中,则应在数据库表中定义角色,而不要使用枚举。这样可以为您提供参照完整性。


0

如果系统(可能是软件,操作系统或组织)处理角色和权限,那么有时候添加角色和管理角色很有用。
如果可以通过更改源代码将其归档,则可以坚持枚举。但是在某个时候,系统用户希望能够管理角色和权限。枚举变得无法使用。
相反,您将需要具有可配置的数据结构。即UserRole类,该类还拥有分配给角色的一组权限。
User类将具有一组角色。如果需要检查用户的权限,则会创建所有角色权限的并集,并检查其中是否包含所涉及的权限。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.