Đó có phải là một thực hành tốt để sử dụng Danh sách Enums?


32

Tôi hiện đang làm việc trên một hệ thống có Người dùng và mỗi người dùng có một hoặc nhiều vai trò. Có phải là một cách thực hành tốt để sử dụng Danh sách các giá trị Enum trên Người dùng? Tôi không thể nghĩ bất cứ điều gì tốt hơn, nhưng điều này không cảm thấy ổn.

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

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

6
Có vẻ tốt với tôi, tôi sẽ thấy thích thú khi thấy những bình luận của người khác ngược lại.
David Scholefield

9
@MatthewRock đó là một khái quát khá sâu rộng. Danh sách <T> khá phổ biến trong thế giới .NET.
Graham

7
@MatthewRock .NET List là một danh sách mảng, có cùng thuộc tính cho các thuật toán bạn đề cập.
rất bối rối

18
@MatthewRock - không, bạn đang nói về danh sách LIÊN KẾT, khi câu hỏi và mọi người khác đang nói về giao diện Danh sách chung.
Thưởng thức Ždralo

5
Thuộc tính tự động là một tính năng đặc biệt của C # (cú pháp get; set; cú pháp). Ngoài ra việc đặt tên lớp List.
jaypb

Câu trả lời:


37

TL; DR: Thường là một ý tưởng tồi khi sử dụng một bộ sưu tập các enum vì nó thường dẫn đến một thiết kế xấu. Một tập hợp các enum thường gọi các thực thể hệ thống riêng biệt với logic cụ thể.

Cần phân biệt giữa một vài trường hợp sử dụng enum. Danh sách này chỉ đứng đầu tôi nên có thể có nhiều trường hợp hơn ...

Các ví dụ đều có trong C #, tôi đoán ngôn ngữ bạn chọn sẽ có cấu trúc tương tự hoặc bạn có thể tự thực hiện chúng.

1. Chỉ một giá trị duy nhất là hợp lệ

Trong trường hợp này, các giá trị là độc quyền, vd

public enum WorkStates
{
    Init,
    Pending,
    Done
}

Nó là không hợp lệ để có một số công việc là cả hai PendingDone. Do đó, chỉ một trong những giá trị này là hợp lệ. Đây là một trường hợp sử dụng tốt của enum.

2. Kết hợp các giá trị là hợp lệ
Trường hợp này còn được gọi là cờ, C # cung cấp [Flags]thuộc tính enum để làm việc với các giá trị này. Ý tưởng có thể được mô hình thành một tập hợp bools hoặcbit s với mỗi tương ứng với một thành viên enum. Mỗi thành viên nên có một giá trị sức mạnh của hai. Kết hợp có thể được tạo bằng các toán tử bitwise:

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

Sử dụng một tập hợp các thành viên enum là một việc quá mức trong trường hợp như vậy vì mỗi thành viên enum chỉ đại diện cho một bit được đặt hoặc không được đặt. Tôi đoán hầu hết các ngôn ngữ hỗ trợ các cấu trúc như thế này. Nếu không, bạn có thể tạo một (ví dụ: sử dụngbool[] và giải quyết nó bằng cách (1 << (int)YourEnum.SomeMember) - 1).

a) Tất cả các kết hợp là hợp lệ

Mặc dù những điều này là ổn trong một số trường hợp đơn giản, một bộ sưu tập các đối tượng có thể phù hợp hơn vì bạn thường cần thêm thông tin hoặc hành vi dựa trên loại.

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

(lưu ý: điều này giả sử bạn chỉ thực sự quan tâm đến hương vị của kem - rằng bạn không cần phải mô hình hóa kem như một bộ sưu tập muỗng và hình nón)

b) Một số kết hợp các giá trị là hợp lệ và một số không

Đây là một kịch bản thường xuyên. Trường hợp thường có thể là bạn đặt hai thứ khác nhau vào một enum. Thí dụ:

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

Bây giờ trong khi nó hoàn toàn hợp lệ cho cả hai VehicleBuildingDoors và Windows, nó không phải là rất bình thường đối với Buildings có Wheels.

Trong trường hợp này, tốt hơn là chia enum thành các phần và / hoặc sửa đổi hệ thống phân cấp đối tượng để đạt được trường hợp # 1 hoặc # 2a).

Thiết kế cân nhắc

Bằng cách nào đó, enum có xu hướng không phải là yếu tố thúc đẩy trong OO vì loại thực thể có thể được coi là tương tự như thông tin thường được cung cấp bởi một enum.

Lấy ví dụ IceCreammẫu từ # 2,IceCream thực thể thay vì cờ có một bộ sưu tập các Scoopđối tượng.

Cách tiếp cận ít thuần túy hơn sẽ là Scoopđể có một Flavortài sản. Cách tiếp cận thuần túy sẽ Scooplà một lớp cơ sở trừu tượng cho VanillaScoop,ChocolateScoop , ... các lớp học để thay thế.

Điểm mấu chốt là:
1. Không phải tất cả mọi thứ là "loại của một cái gì đó" cần phải được enum
2. Khi một số thành viên enum không phải là cờ hợp lệ trong một số trường hợp, hãy xem xét chia enum thành nhiều enum khác nhau.

Bây giờ cho ví dụ của bạn (thay đổi một chút):

public enum Role
{
    User,
    Admin
}

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

Tôi nghĩ trường hợp chính xác này nên được mô hình hóa thành (lưu ý: không thực sự mở rộng!):

public class User
{
    public bool IsAdmin { get; set; }
}

Nói cách khác - đó là ẩn ý, ​​rằng đó Userlà một User, thông tin bổ sung là liệu anh ta có phải là mộtAdmin .

Nếu bạn có thể có nhiều vai trò mà không phải là độc quyền (ví dụ như Usercó thể Admin, Moderator, VIP, ... cùng một lúc), đó sẽ là một thời điểm tốt để sử dụng một trong hai lá cờ enum hoặc một lớp cơ sở TÓM TẮT hoặc giao diện.

Sử dụng một lớp để thể hiện một Rolekhách hàng tiềm năng để phân tách trách nhiệm tốt hơn trong đó một người Rolecó thể có trách nhiệm quyết định liệu họ có thể thực hiện một hành động nhất định hay không.

Với một enum, bạn cần có logic ở một nơi cho tất cả các vai trò. Mà đánh bại mục đích của OO và đưa bạn trở lại mệnh lệnh.

Hãy tưởng tượng rằng a Moderatorcó quyền chỉnh sửa và Admincó cả quyền chỉnh sửa và xóa.

Cách tiếp cận Enum (được gọi Permissionsđể không trộn lẫn vai trò và quyền):

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

Cách tiếp cận lớp học (điều này không hoàn hảo, lý tưởng nhất là bạn muốn IActionmô hình khách truy cập nhưng bài đăng này đã rất lớn ...):

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

Sử dụng một enum có thể là một cách tiếp cận chấp nhận được (ví dụ: hiệu suất). Quyết định ở đây phụ thuộc vào yêu cầu của hệ thống mô hình.


3
Tôi không nghĩ rằng [Flags]ngụ ý tất cả các kết hợp là hợp lệ hơn một int ngụ ý tất cả bốn tỷ giá trị của loại đó là hợp lệ. Nó đơn giản có nghĩa là chúng có thể được kết hợp trong một trường duy nhất - mọi hạn chế đối với các kết hợp thuộc về logic cấp cao hơn.
Random832

Cảnh báo: khi sử dụng [Flags], bạn nên đặt các giá trị thành lũy thừa của hai hoặc nếu không nó sẽ không hoạt động như mong đợi.
Arturo Torres Sánchez

@ Random832 Tôi chưa bao giờ có ý định nói điều đó nhưng tôi đã chỉnh sửa câu trả lời - hy vọng nó rõ ràng hơn bây giờ.
Zdeněk Jelínek

1
@ ArturoTorresSánchez Cảm ơn bạn đã đóng góp, tôi đã sửa câu trả lời và cũng đã thêm một ghi chú giải thích về điều đó.
Zdeněk Jelínek

Giải thích tuyệt vời! Trên thực tế, cờ phù hợp với yêu cầu hệ thống rất tốt, tuy nhiên việc sử dụng HashSets sẽ dễ dàng hơn do triển khai dịch vụ hiện có.
Dexie

79

Tại sao không sử dụng Set? Nếu sử dụng Danh sách:

  1. Thật dễ dàng để thêm vai trò hai lần
  2. So sánh ngây thơ các danh sách sẽ không hoạt động chính xác ở đây: [Người dùng, Quản trị viên] không giống với [Quản trị viên, Người dùng]
  3. Các hoạt động phức tạp như giao nhau và hợp nhất không đơn giản để thực hiện

Nếu bạn quan tâm đến hiệu năng, thì, ví dụ như trong Java, có một EnumSetphần được thực hiện dưới dạng mảng booleans có độ dài cố định (phần tử boolean trả lời câu hỏi liệu giá trị Enum có xuất hiện trong tập hợp này hay không). Ví dụ , EnumSet<Role>. Ngoài ra, xem EnumMap. Tôi nghi ngờ rằng C # có một cái gì đó tương tự.


35
Trên thực tế trong Java EnumSets được triển khai dưới dạng các trường bit.
biziclop

4
Câu hỏi SO liên quan về HashSet<MyEnum>so với cờ enums: stackoverflow.com/q/9077487/87698
Heinzi

3
.NET có FlagsAttribute, cho phép bạn sử dụng các toán tử bitwise để ghép enum. Âm thanh tương tự như Java EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribution EDIT: Tôi nên xem thường một câu trả lời! nó có một ví dụ tốt về điều này.
ps2goat

4

Viết enum của bạn để bạn có thể kết hợp chúng. Bằng cách sử dụng cơ sở 2 theo cấp số nhân, bạn có thể kết hợp tất cả chúng trong một enum, có 1 thuộc tính và có thể kiểm tra chúng. Enum của bạn nên lile này

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

3
Bất kỳ lý do tại sao bạn không sử dụng 1?
Robbie Dee

1
@RobbieDee không có hiệu quả Tôi có thể đã sử dụng 1, 2, 4, 8, v.v ... bạn nói đúng
Rémi

5
Chà, nếu bạn sử dụng Java, thì nó đã EnumSetthực hiện điều này cho enums. Bạn có tất cả các số học boolean đã được trừu tượng hóa, cộng với một số phương pháp khác để làm cho nó dễ sử dụng hơn, cộng với bộ nhớ nhỏ và hiệu suất tốt.
fr13d

2
@ fr13d Tôi là anh chàng ac # và vì câu hỏi không có thẻ java nên tôi nghĩ câu trả lời này được áp dụng nhiều hơn
Rémi

1
Phải rồi, @ Rémi. C # cung cấp [flags]thuộc tính, mà @ Zdeněk Jelínek khám phá trong bài đăng của mình .
fr13d

4

Có phải là một cách thực hành tốt để sử dụng Danh sách các giá trị Enum trên Người dùng?


Câu trả lời ngắn gọn : Có


Câu trả lời ngắn hơn : Có, enum định nghĩa một cái gì đó trong miền.


Câu trả lời theo thời gian thiết kế : Tạo và sử dụng các lớp, cấu trúc, v.v ... mô hình hóa miền theo chính miền đó.


Mã hóa thời gian trả lời : Đây là cách mã en-sling ...


Các câu hỏi được suy luận :

  • Có nên sử dụng chuỗi?

    • Trả lời: Không.
  • Tôi có nên sử dụng một số lớp khác?

    • Ngoài ra, vâng. Thay vì, không.
  • Roledanh sách enum đủ để sử dụng trong User?

    • Tôi không có ý kiến. Nó phụ thuộc rất nhiều vào các chi tiết thiết kế khác không có trong bằng chứng.

WTF Bob?

  • Đây là một câu hỏi thiết kế được đóng khung trong kỹ thuật ngôn ngữ lập trình.

    • An enumlà một cách tốt để xác định "Vai trò" trong mô hình của bạn. Vì vậy, một Listvai trò là một điều tốt.
  • enum là vượt trội so với chuỗi.

    • Role là một tuyên bố về TẤT CẢ các vai trò tồn tại trong miền.
    • Chuỗi "Quản trị" là một chuỗi có các chữ cái "A" "d" "m" "i" "n"theo thứ tự đó. Nó không có gì xa như tên miền có liên quan.
  • Bất kỳ lớp nào bạn có thể thiết kế để đưa ra khái niệm về Rolemột số cuộc sống - chức năng - có thể sử dụng tốt Roleenum.

    • Ví dụ, thay vì lớp phụ cho mọi loại vai trò, có một thuộc Roletính cho biết loại vai trò của lớp này là gì.
    • Một User.Rolesdanh sách là hợp lý khi chúng ta không cần, hoặc không muốn, các đối tượng vai trò khởi tạo.
    • Và khi chúng ta cần khởi tạo một vai trò, enum là một cách không mơ hồ, loại an toàn để truyền đạt điều đó đến một RoleFactorylớp; và, không đáng kể, cho người đọc mã.

3

Đó không phải là thứ tôi đã thấy nhưng tôi thấy không có lý do tại sao nó không hoạt động. Tuy nhiên, bạn có thể muốn sử dụng danh sách đã sắp xếp để bảo vệ chống lại bản sao.

Một cách tiếp cận phổ biến hơn là một cấp độ người dùng, ví dụ: hệ thống, quản trị viên, người dùng quyền lực, người dùng, vv Hệ thống có thể làm mọi thứ, quản trị viên hầu hết mọi thứ, v.v.

Nếu bạn định đặt các giá trị của vai trò là quyền hạn của hai, bạn có thể lưu trữ vai trò như một số nguyên và lấy các vai trò nếu bạn thực sự muốn lưu trữ chúng trong một trường duy nhất nhưng điều đó có thể không phù hợp với sở thích của mọi người.

Vì vậy, bạn có thể có:

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

Vì vậy, nếu người dùng có giá trị vai trò là 6 thì họ sẽ có vai trò Kho văn phòng và Kho.


2

Sử dụng Hashset vì nó ngăn ngừa trùng lặp và có nhiều phương thức so sánh hơn

Nếu không thì sử dụng tốt Enum

Thêm một phương thức Boolean IsInRole (Vai trò R)

và tôi sẽ bỏ qua bộ

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

1

Ví dụ đơn giản mà bạn đưa ra là tốt, nhưng thường thì nó phức tạp hơn thế. Ví dụ, nếu bạn định duy trì người dùng và vai trò trong cơ sở dữ liệu, thì bạn nên xác định vai trò trong bảng cơ sở dữ liệu thay vì sử dụng enums. Điều đó mang lại cho bạn tính toàn vẹn tham chiếu.


0

Nếu một hệ thống - có thể là phần mềm, hệ điều hành hoặc tổ chức - liên quan đến vai trò và sự cho phép, sẽ đến lúc hữu ích để thêm vai trò và quản lý quyền cho chúng.
Nếu điều này có thể được lưu trữ bằng cách thay đổi mã nguồn, có thể ổn, để gắn với enums. Nhưng đến một lúc nào đó, người dùng hệ thống muốn có thể quản lý vai trò và quyền. Than enum trở nên không thể sử dụng.
Thay vào đó, bạn sẽ muốn có một cấu trúc dữ liệu cấu hình. tức là một lớp UserRole cũng giữ một tập các quyền được gán cho vai trò.
Một lớp người dùng sẽ có một tập hợp các vai trò. Nếu có nhu cầu kiểm tra sự cho phép của người dùng, một tập hợp tất cả các quyền vai trò sẽ được tạo và kiểm tra nếu nó có quyền trong câu hỏi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.