열거 형 목록을 사용하는 것이 좋습니다?


32

현재 사용자가있는 시스템에서 작업하고 있으며 각 사용자에게는 하나 이상의 역할이 있습니다. User에서 Enum 값 목록을 사용하는 것이 좋습니다? 나는 더 나은 것을 생각할 수 없지만 이것은 기분이 좋지 않습니다.

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

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

6
나에게 잘 어울린다. 나는 다른 사람의 의견을 반대로보고 싶어한다.
David Scholefield

9
@MatthewRock은 꽤 포괄적 인 일반화입니다. List <T>는 .NET 세계에서 매우 일반적입니다.
Graham

7
@MatthewRock .NET 목록은 언급 한 알고리즘에 대해 동일한 속성을 갖는 배열 목록입니다.
메신저 너무 혼란

18
@MatthewRock-아니오, 당신은 질문과 다른 사람들이 일반 목록 인터페이스에 대해 이야기 할 때 링크 된 목록에 대해 이야기하고 있습니다.
Davor Ždralo

5
자동 속성은 C #의 고유 한 기능입니다 (get; set; 구문). 또한 List 클래스 이름 지정.
jaypb

답변:


37

TL; DR : 일반적으로 디자인이 잘못되기 때문에 열거 형 모음을 사용하는 것은 좋지 않습니다. 열거 형 모음은 일반적으로 특정 논리를 가진 고유 한 시스템 엔터티를 요구합니다.

열거 형의 몇 가지 사용 사례를 구별해야합니다. 이 목록은 내 머리 위에 만 있으므로 더 많은 사례가있을 수 있습니다 ...

예제는 모두 C #에 있으며, 선택한 언어가 비슷한 구문을 갖거나 직접 구현할 수 있습니다.

1. 단일 값만 유효합니다

이 경우 값은 배타적입니다. 예 :

public enum WorkStates
{
    Init,
    Pending,
    Done
}

Pending과 (과 ) 모두 같은 작업을하는 것은 유효하지 않습니다 Done. 따라서이 값 중 하나만 유효합니다. 이것은 열거 형의 좋은 사용 사례입니다.

2. 값의 조합이 유효합니다.
이 경우는 플래그라고도하며 C #은 [Flags]이러한 작업에 열거 형 속성을 제공합니다 . 아이디어는 각각 하나의 열거 형 멤버에 해당하는 bools 또는 bits 세트로 모델링 될 수 있습니다 . 각 구성원은 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; }
}

이 모두 완전하게 유효한있는 동안 지금 VehicleBuilding가지고 Doors와 Window의를 위해, 아주 보통이 아니다 Building들 가지고 Wheel들.

이 경우, 열거 # 1 또는 # 2a를 달성하기 위해 열거를 부분으로 나누거나 객체 계층을 수정하는 것이 좋습니다.

디자인 고려 사항

어쨌든 열거 형은 OO의 추진 요소가 아닌 경향이 있습니다. 엔티티의 유형은 일반적으로 열거 형이 제공하는 정보와 유사하게 간주 될 수 있기 때문입니다.

예를 들어 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; }
}

즉 - 그것은 그, 암시 적 UserA는 User, 추가 정보는 그가 여부입니다 Admin.

당신이 배타적이지 여러 역할을 할 수있는 경우 (예를 들어이 User될 수있다 Admin, Moderator, VIP, ... 같은 시간에), 그 중 하나가 플래그 열거 형 사용하는 것이 시간 또는 abtract 기본 클래스 또는 인터페이스가 될 것입니다.

클래스를 사용하여 주어진 조치를 수행 할 수 있는지 여부를 결정할 책임이있는 책임을 Role더 잘 분리 Role합니다.

열거 형을 사용하면 모든 역할에 대해 한곳에 논리가 있어야합니다. 어느 것이 OO의 목적을 무너 뜨리고 다시 명령해야합니다.

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는 그 유형의 40 억 값이 모두 유효하다는 것을 의미합니다. 이는 단순히 단일 필드 내에서 결합 될 있음을 의미 합니다. 조합에 대한 제한은 상위 논리에 속합니다.
Random832

경고 :를 사용할 때는 [Flags]값을 2의 거듭 제곱으로 설정해야합니다. 그렇지 않으면 예상대로 작동하지 않습니다.
Arturo Torres Sánchez

@ Random832 나는 그런 말을 할 의도가 없었지만 대답을 편집했습니다. 지금 명확 해지기를 바랍니다.
Zdeněk Jelínek 2012 년

1
@ ArturoTorresSánchez 의견을 보내 주셔서 감사합니다. 답변을 수정했으며 이에 대한 설명을 추가했습니다.
Zdeněk Jelínek 2012 년

좋은 설명! 실제로 플래그는 시스템 요구 사항에 매우 적합하지만 기존 서비스 구현으로 인해 HashSet을 사용하는 것이 더 쉽습니다.
프록시

79

왜 Set을 사용하지 않습니까? 목록을 사용하는 경우 :

  1. 같은 역할을 두 번 쉽게 추가 할 수 있습니다
  2. 목록의 순진한 비교는 여기서 제대로 작동하지 않습니다. [User, Admin]은 [Admin, User]와 다릅니다
  3. 교차 및 병합과 같은 복잡한 작업은 간단하지 않습니다.

예를 들어 Java와 같은 성능에 관심이 있다면 EnumSet고정 길이의 부울 배열로 구현됩니다 (i 번째 부울 요소는 i'th Enum 값 이이 세트에 있는지 여부에 대한 질문에 대답합니다). 예를 들면 다음과 같습니다 EnumSet<Role>. 또한을 참조하십시오 EnumMap. C #에 비슷한 것이 있다고 생각합니다.


35
실제로 Java EnumSet에서는 비트 필드로 구현됩니다.
biziclop

4
HashSet<MyEnum>플래그 열거 형 에 대한 관련 SO 질문 : stackoverflow.com/q/9077487/87698
Heinzi

3
.NET에는 FlagsAttribute을 사용하여 비트 연산자를 사용하여 열거 형을 연결할 수 있습니다. 자바와 비슷한 소리 EnumSet. msdn.microsoft.com/en-US/LIBRARY/system.flagsattribute 편집 : 하나의 답변을 살펴보아야합니다! 이것의 좋은 예가 있습니다.
ps2goat

4

열거 형을 작성하여 결합 할 수 있습니다. 기수 2 지수를 사용하면 하나의 열거 형으로 모두 결합하고 1 개의 속성을 가지며 확인할 수 있습니다. 당신의 열거 형은 이것이어야합니다.

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

3
1을 사용하지 않는 이유는 무엇입니까?
Robbie Dee

1
@RobbieDee 효과적으로 1, 2, 4, 8 등을 사용할 수 없었습니다.
Rémi

5
글쎄, 자바를 사용한다면 이미 EnumSet이것을 구현 한 것이있다 enum. 모든 부울 산술이 이미 추상화되었으며 사용하기 쉽게 만드는 다른 방법과 작은 메모리 및 우수한 성능이 있습니다.
fr13d

2
@ fr13d 저는 ac #이며 질문에 Java 태그가 없기 때문에이 답변이 더 많이 적용된다고 생각합니다.
Rémi

1
맞습니다, @ Rémi. C #은 [flags]@ Zdeněk Jelínek이 자신의 게시물 에서 탐색 하는 속성을 제공합니다 .
fr13d

4

User에서 Enum 값 목록을 사용하는 것이 좋습니다?


짧은 답변 : 예


더 나은 짧은 대답 : 예, 열거 형은 도메인에서 무언가를 정의합니다.


디자인 타임 답변 : 도메인 자체를 기준으로 도메인을 모델링하는 클래스, 구조 등을 만들고 사용합니다.


코딩 시간 답변 : 다음은 열거 형을 코드 슬링하는 방법입니다 ...


유추 된 질문 :

  • 문자열을 사용해야합니까?

    • 답 : 아니요.
  • 다른 수업을 사용해야합니까?

    • 또한 그렇습니다. 대신에 아닙니다.
  • 는 IS Role열거 목록에서 사용하기에 충분 User?

    • 나도 몰라 증거가 아닌 다른 설계 세부 사항에 크게 의존합니다.

WTF 밥?

  • 이것은 프로그래밍 언어 기술에 짜여진 디자인 질문입니다.

    • 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 을 사용하십시오.

그렇지 않으면 Enum을 잘 사용하십시오.

부울 IsInRole (Role R) 메소드 추가

그리고 나는 세트를 건너 뛸 것이다

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

1

당신이 제공하는 단순한 예는 훌륭하지만 일반적으로 그보다 더 복잡합니다. 예를 들어 데이터베이스에서 사용자 및 역할을 유지하려는 경우 열거 형을 사용하지 않고 데이터베이스 테이블에서 역할을 정의해야합니다. 참조 무결성을 제공합니다.


0

시스템 (소프트웨어, 운영 체제 또는 조직)이 역할 및 권한을 처리하는 경우 역할을 추가하고 해당 시스템의 권한을 관리하는 것이 유용한 시점이 있습니다.
소스 코드를 변경하여 아카이브 할 수 있다면 열거 형을 고수하는 것이 좋습니다. 그러나 어느 시점에서 시스템 사용자는 역할과 권한을 관리 할 수 ​​있기를 원합니다. 열거 형을 사용할 수 없게됩니다.
대신 구성 가능한 데이터 구조를 원할 것입니다. 즉, 역할에 할당 된 일련의 권한을 보유한 UserRole 클래스.
User 클래스에는 일련의 역할이 있습니다. 사용자의 권한을 확인해야하는 경우 모든 역할 권한의 통합 집합이 만들어지고 해당 권한이 포함되어 있는지 확인합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.