열거 형이 취성 인터페이스를 작성합니까?


17

아래 예를 고려하십시오. ColorChoice 열거 형을 변경하면 모든 IWindowColor 하위 클래스에 영향을줍니다.

열거 형은 취성 인터페이스를 유발하는 경향이 있습니까? 더 다형성 유연성을 허용하는 열거 형보다 좋은 것이 있습니까?

enum class ColorChoice
{
    Blue = 0,
    Red = 1
};

class IWindowColor
{
public:
    ColorChoice getColor() const=0;
    void setColor( const ColorChoice value )=0;
};

편집 : 내 예제로 색상을 사용하여 죄송합니다. 질문이 아닙니다. 다음은 붉은 청어를 피하고 유연성의 의미에 대한 자세한 정보를 제공하는 다른 예입니다.

enum class CharacterType
{
    orc = 0,
    elf = 1
};

class ISomethingThatNeedsToKnowWhatTypeOfCharacter
{
public:
    CharacterType getCharacterType() const;
    void setCharacterType( const CharacterType value );
};

또한, 적절한 ISomethingThatNeedsToKnowWhatTypeOfCharacter 서브 클래스에 대한 핸들이 팩토리 디자인 패턴에 의해 전달된다고 가정하십시오. 이제 허용 가능한 문자 유형이 {human, dwarf} 인 다른 응용 프로그램에 대해 향후 확장 할 수없는 API가 있습니다.

편집 : 내가하고있는 일에 대해 더 구체적으로하십시오. 이 ( MusicXML ) 사양 의 강력한 바인딩을 설계하고 있으며 xs : enumeration으로 선언 된 사양에서 이러한 유형을 나타내는 열거 형 클래스를 사용하고 있습니다. 다음 버전 (4.0)이 나올 때 어떻게되는지 생각하려고합니다. 수업 라이브러리가 3.0 모드와 4.0 모드에서 작동 할 수 있습니까? 다음 버전이 100 % 이전 버전과 호환되는 경우 가능합니다. 그러나 열거 값이 사양에서 제거되면 물에서 죽었습니다.


2
"다형성 유연성"이라고 말할 때 정확히 어떤 기능을 염두에두고 있습니까?
Ixrec


3
색상에 enum을 사용하면 "enum 사용"뿐만 아니라 취성 인터페이스도 생성됩니다.
Doc Brown

3
새로운 열거 형 변형을 추가하면 해당 열거 형을 사용하는 코드가 중단됩니다. 반면에 처리 해야하는 모든 경우가 바로 거기에 있기 때문에 열거 형에 새 작업 을 추가하는 것은 자체적으로 포함되어 있습니다 (기본이 아닌 메소드를 추가하는 것이 심각한 변화가되는 인터페이스 및 수퍼 클래스와 대조적 임). 실제로 필요한 변경 종류에 따라 다릅니다.

1
ReMusicXML : XML 파일에서 각 스키마가 사용하는 스키마 버전을 쉽게 알 수있는 방법이 없다면 사양에서 중요한 디자인 결함으로 생각됩니다. 어떻게 든 해결 방법이 필요한 경우 4.0에서 어떤 항목을 선택해야하는지 정확히 알기 전까지는 도움을 줄 수있는 방법이 없을 것입니다.
Ixrec

답변:


25

올바르게 사용하면 열거 형은 대체하는 "마법의 숫자"보다 훨씬 읽기 쉽고 강력합니다. 일반적으로 코드가 더 부서지기 쉬운 것으로 보이지 않습니다. 예를 들어 :

  • setColor ()는 value유효한 색상 값인지 여부를 확인하는 데 시간을 낭비하지 않아도됩니다 . 컴파일러는 이미 그렇게했습니다.
  • setColor (0) 대신 setColor (Color :: Red)를 쓸 수 있습니다. enum class현대 C ++ 의 기능을 사용하면 사람들이 항상 후자 대신 전자를 쓰도록 강요 할 수 있습니다.
  • 일반적으로 중요하지는 않지만 대부분의 열거 형은 모든 크기 통합 유형으로 구현할 수 있으므로 컴파일러는 그러한 것들에 대해 생각하지 않고도 가장 편리한 크기를 선택할 수 있습니다.

그러나 많은 (대부분의) 상황에서 사용자를 이러한 작은 색상 세트로 제한 할 이유가 없기 때문에 색상에 열거 형을 사용하는 것은 의문 의 여지가 있습니다. 임의의 RGB 값을 전달하도록 할 수도 있습니다. 내가 작업하는 프로젝트에서 이와 같은 작은 색상 목록은 구체적인 색상에 대한 얇은 추상화로 작용해야하는 "테마"또는 "스타일"세트의 일부로 만 나타납니다.

귀하의 "다형성 유연성"문제가 무엇인지 잘 모르겠습니다. 열거 형에는 실행 코드가 없으므로 다형성을 만들 필요가 없습니다. 아마도 당신은 명령 패턴을 찾고 있습니까?

편집 : 편집 후, 여전히 어떤 종류의 확장 성을 찾고 있는지 명확하지 않지만 여전히 명령 패턴이 "다형성 열거 형"에 가장 근접한 것으로 생각합니다.


0이 열거 형으로 전달되지 않도록하는 방법에 대한 자세한 내용은 어디에서 찾을 수 있습니까?
TankorSmash

5
@TankorSmash C ++ 11은 암시 적으로 기본 숫자 유형으로 변환 할 수없는 "범위 열거"라고도하는 "enum 클래스"를 도입했습니다. 또한 이전 C 스타일 "enum"유형과 마찬가지로 네임 스페이스를 오염시키지 않습니다.
Matthew James Briggs

2
열거 형은 일반적으로 정수로 백업됩니다. 직렬화 / 역 직렬화 또는 정수와 열거 형 사이의 캐스팅에서 발생할 수있는 이상한 일이 많이 있습니다. 열거 형에 항상 유효한 값이 있다고 가정하는 것이 항상 안전하지는 않습니다.
Eric

1
당신은 맞습니다 Eric (그리고 이것은 내가 여러 번 직면 한 문제입니다). 그러나 역 직렬화 단계에서 유효하지 않은 값에 대해서만 걱정하면됩니다. 열거 형을 사용하는 다른 모든 경우에는 값이 유효하다고 가정 할 수 있습니다 (적어도 스칼라와 같은 언어의 열거 형의 경우 일부 언어는 열거 형에 대한 유형 검사가 매우 강력하지 않습니다).
Kat

1
@Ixrec "많은 (대부분의) 상황에서 사용자를 이러한 작은 색상 세트로 제한 할 이유가 없습니다"합법적 인 사례가 있습니다. .Net 콘솔은 16 가지 색상 중 하나 (CGA 표준의 16 가지 색상) 중 하나의 텍스트 만 가질 수있는 구식 Windows 콘솔을 에뮬레이트합니다. msdn.microsoft.com/en-us/library/…
Pharap

15

ColorChoice 열거 형을 변경하면 모든 IWindowColor 하위 클래스에 영향을줍니다.

아닙니다. 두 가지 경우가 있습니다 : 구현자는

  • 열거 형 값을 저장, 반환 및 전달하고 절대로 작동하지 않습니다.이 경우 열거 형 변경에 영향을받지 않습니다.

  • 개별 열거 형 값에 대해 작동하며,이 경우 열거 형의 모든 변경은 당연히 당연히 불가피하게도 반드시 구현 자의 논리에 상응하는 변경을 고려해야합니다.

"Sphere", "Rectangle"및 "Pyramid"를 "Shape"열거 형에 넣고 해당 열거 형을 drawSolid()해당 솔리드를 그리기 위해 작성한 함수에 전달한 다음 어느 날 아침에 " Ikositetrahedron "값을 열거 형에 적용하면 drawSolid()함수가 영향을받지 않을 것으로 예상 할 수 없습니다 . icositetrahedrons를 그리기 위해 실제 코드를 먼저 작성하지 않고도 icositetrahedrons를 그릴 것으로 예상했다면, 그것은 열거 형의 결함이 아니라 당신의 잘못입니다. 그래서:

열거 형은 취성 인터페이스를 유발하는 경향이 있습니까?

아닙니다. 취약한 인터페이스의 원인은 프로그래머가 자신을 닌자로 생각하고 충분한 경고를 사용하지 않고 코드를 컴파일하려고 시도하는 것입니다. 그런 다음 컴파일러는 drawSolid()함수 에 새로 추가 된 "Ikositetrahedron"열거 형 값에 switch대한 case절이 누락 된 명령문이 있음을 경고하지 않습니다 .

그것이 작동하는 방식은 새로운 순수 가상 메소드를 기본 클래스에 추가하는 것과 유사합니다. 그러면 모든 단일 상속자에 대해이 메소드를 구현해야합니다. 그렇지 않으면 프로젝트가 빌드되지 않아야합니다.


이제 열거 형은 객체 지향 구조가 아닙니다. 그것들은 객체 지향 패러다임과 구조화 된 프로그래밍 패러다임 사이의 실질적인 타협입니다.

일을 수행하는 순수한 객체 지향 방식은 전혀 열거 형이 아니며 객체를 완전히 보유하는 것입니다.

따라서 솔리드로 예제를 구현하는 순수한 객체 지향 방법은 물론 다형성을 사용하는 것입니다. 모든 것을 그리는 방법을 알고 단일 솔리드를 그리는 방법을 알고있는 단일 중앙 집중식 방법 대신 " 추상 (순수 가상) draw()메소드를 사용하는 솔리드 "클래스를 작성한 다음"스피어 ","사각형 "및"피라미드 "서브 클래스를 추가 draw()합니다.

이런 식으로 "Ikositetrahedron"서브 클래스를 소개 할 때 draw()함수 를 제공하면 되고 컴파일러는 "Icositetrahedron"을 인스턴스화하지 않도록함으로써이를 수행하도록 상기시켜줍니다.


해당 스위치에 대해 컴파일 타임 경고를 던지는 방법에 대한 팁이 있습니까? 나는 보통 런타임 예외를 던진다. 그러나 컴파일 시간은 좋을 수 있습니다! 별로 좋지는 않지만 단위 테스트가 떠 오릅니다.
Vaughan Hilts

1
마지막으로 C ++을 사용한 지 얼마되지 않아서 확실하지 않지만 컴파일러에 -Wall 매개 변수를 제공하고 default:절을 생략하면 됩니다. 주제에 대한 빠른 검색은 더 이상 명확한 결과를 얻지 못하므로 다른 programmers SE질문 의 주제로 적합 할 수 있습니다 .
Mike Nakis

C ++에서는 컴파일 시간 검사를 활성화 할 수 있습니다. C #에서는 모든 검사를 런타임에 수행해야합니다. 플래그가 아닌 열거 형을 매개 변수로 사용할 때마다 Enum.IsDefined로 유효성을 검사해야하지만 새 값을 추가 할 때 열거 형의 모든 사용을 수동으로 업데이트해야합니다. 참조 : stackoverflow.com/questions/12531132/...
마이크 모니카 지원

1
객체 지향 프로그래머가 Pyramid실제로 draw()피라미드 를 만드는 방법을 알고있는 것처럼 데이터 전송 객체를 만들지 않기를 바랍니다 . 기껏해야 파생되어 메소드를 Solid가질 GetTriangles()수 있으며이를 SolidDrawer서비스에 전달할 수 있습니다. 우리는 OOP에서 객체의 예로 물리적 객체의 예에서 벗어나고 있다고 생각했습니다.
Scott Whitlock

1
괜찮습니다. 오래된 객체 지향 프로그래밍을 강타하는 사람을 좋아하지 않았습니다. :) 특히 우리 대부분은 기능 프로그래밍과 OOP를 더 이상 OOP보다 더 많이 결합하기 때문입니다.
Scott Whitlock

14

열거 형은 취성 인터페이스를 만들지 않습니다. 열거 형의 오용 은 않습니다.

열거 형이란 무엇입니까?

열거 형은 의미있는 이름의 상수 집합으로 사용되도록 설계되었습니다. 다음과 같은 경우에 사용됩니다.

  • 값이 제거되지 않음을 알고 있습니다.
  • (그리고) 새로운 가치가 필요할 것 같지는 않습니다.
  • (또는) 귀하는 새로운 가치가 필요하다는 것을 인정하지만, 그로 인해 파기되는 모든 코드를 수정해야 할 정도는 아닙니다.

열거 형의 올바른 사용법 :

  • 요일 : (.Net 's에 따라 System.DayOfWeek) 엄청나게 모호한 캘린더를 다루지 않는 한, 요일은 7 일입니다.
  • 확장 할 수없는 색상 : (.Net 's에 따라 System.ConsoleColor) 일부는 이에 동의하지 않을 수 있지만, .Net은 이유 때문에이를 선택했습니다. .Net의 콘솔 시스템에는 16 가지 색상 만 사용할 수 있습니다. 이 16 가지 색상은 CGA 또는 '컬러 그래픽 어댑터' 라고하는 레거시 색상 팔레트에 해당합니다 . 새로운 값이 더 이상 추가되지 않으므로 이는 실제로 열거 형을 합리적으로 적용하는 것입니다.
  • 고정 된 상태를 나타내는 열거 : (Java에 따라) Java Thread.State설계자는 Java 스레딩 모델에는 고정 상태 세트 만있을 것이라고 결정했습니다. Thread따라서 문제를 단순화하기 위해 이러한 다른 상태는 열거로 표시됩니다. . 이것은 많은 상태 기반 검사가 실제 값에 대해 걱정할 필요없이 실제로 정수 값에서 작동하는 간단한 if 및 스위치라는 것을 의미합니다.
  • 상호 배타적이지 않은 독점 옵션을 나타내는 Bitflags : (.Net 's에 따라 System.Text.RegularExpressions.RegexOptions) Bitflags는 열거 형을 매우 일반적으로 사용합니다. 실제로 .Net에서 모든 열거 형에는 HasFlag(Enum flag)메서드가 내장되어 있습니다. 또한 비트 연산자를 지원하며 비트 FlagsAttribute플래그 집합으로 사용되도록 열거 형을 표시하는 a 가 있습니다. 열거 형을 플래그 집합으로 사용하면 편의를 위해 플래그의 이름을 명확하게 지정할 수있을뿐만 아니라 부울 값 그룹을 단일 값으로 나타낼 수 있습니다. 이것은 에뮬레이터에서 상태 레지스터의 플래그를 표시하거나 파일의 권한 (읽기, 쓰기, 실행)을 나타내거나 관련 옵션 세트가 상호 배타적이지 않은 상황에 매우 유용합니다.

열거 형의 나쁜 사용 :

  • 게임에서 캐릭터 클래스 / 유형 : 게임이 다시 사용할 가능성이없는 원샷 데모가 아니라면 클래스를 더 많이 추가하고 싶을 수 있으므로 열거 형을 캐릭터 클래스에 사용해서는 안됩니다. 캐릭터를 나타내는 단일 클래스를 사용하고 게임 내 캐릭터 'type'을 다르게 나타내는 것이 좋습니다. 이를 처리하는 한 가지 방법은 TypeObject 패턴이며, 다른 솔루션으로는 사전 / 유형 레지스트리에 문자 유형을 등록 하거나이 둘을 혼합하여 사용할 수 있습니다.
  • 확장 가능한 색상 : 나중에 추가 할 수있는 색상에 열거 형을 사용하는 경우 열거 형 형식으로 표현하는 것은 좋지 않습니다. 그렇지 않으면 영원히 색상을 추가하게됩니다. 이것은 위의 문제와 유사하므로 유사한 솔루션을 사용해야합니다 (예 : TypeObject의 변형).
  • 확장 가능한 상태 : 더 많은 상태를 도입 할 수있는 상태 시스템이있는 경우 열거를 통해 이러한 상태를 나타내는 것은 좋지 않습니다. 선호되는 방법은 머신의 상태에 대한 인터페이스를 정의하고, 인터페이스를 랩핑하고 해당 메소드 호출 ( 전략 패턴에 영향을 미침)을 위임하는 구현 또는 클래스를 제공 한 다음 제공된 구현이 현재 활성화되어있는 것을 변경하여 상태를 변경하는 것입니다.
  • 상호 배타적 인 옵션을 나타내는 비트 플래그 : 열거 형을 사용하여 플래그를 나타내는 경우 해당 플래그 중 두 개가 함께 발생하지 않아야합니다. 플래그 중 하나에 반응하도록 프로그래밍 된 것은 무엇이든 처음에 반응하도록 프로그래밍 된 플래그에 갑자기 반응합니다. 이런 상황은 문제를 요구하고 있습니다. 가장 좋은 방법은 가능한 경우 True플래그 가없는 경우 대체 조건으로 플래그를 처리하는 것입니다 (즉, 플래그 가 없으면을 의미 함 False). 이 기능은 특수 기능 (예 : IsTrue(flags)IsFalse(flags))을 사용하여 추가로 지원할 수 있습니다 .

누군가가 비트 플래그로 사용되는 열거 형의 작동하거나 잘 알려진 예를 찾을 수 있다면 비트 플래그 예제를 추가 할 것입니다. 나는 그들이 존재한다는 것을 알고 있지만 불행히도 나는 지금 어떤 것도 기억할 수 없습니다.
Pharap


@BLSully 훌륭한 예입니다. 내가 사용한 적이 있다고 말할 수는 없지만 이유가 있습니다.
Pharap

4

열거 형은 관련 기능이 많지 않은 닫힌 값 세트의 마법 식별 번호보다 크게 개선되었습니다. 일반적으로 열거 형과 실제로 어떤 숫자가 연결되어 있는지 상관하지 않습니다. 이 경우 끝에 새 항목을 추가하여 쉽게 확장 할 수 있으므로 취성이 발생하지 않습니다.

문제는 열거 형과 관련된 중요한 기능이있는 경우입니다. 즉, 이런 종류의 코드가 주위에 있습니다.

switch (my_enum) {
case orc: growl(); break;
case elf: sing(); break;
...
}

이것들 중 하나 나 둘은 괜찮지 만, 이런 종류의 의미있는 열거 형을 가지 자마자,이 switch문장들은 떨림처럼 번식하는 경향이 있습니다. 이제 열거 형을 확장 할 때마다 모든 관련 내용을 찾아서 모든 switch내용을 다뤘는지 확인해야합니다. 부서지기 쉬운입니다. Clean Code 와 같은 소스switch 는 최대한 열거 형당 하나가 있어야한다고 제안합니다 .

이 경우 대신해야 할 일은 OO 원칙을 사용 하고이 유형에 대한 인터페이스 를 만드는 것 입니다. 이 유형을 전달하기 위해 열거 형을 계속 유지할 수는 있지만 아무 것도 수행 해야하는 즉시 팩토리를 사용하여 열거 형과 관련된 객체를 만듭니다. 업데이트 할 한 곳 (공장)과 새 클래스를 추가하기 만하면되기 때문에 유지 관리가 훨씬 쉽습니다.


1

사용 방법에주의를 기울이면 열거 형이 해로운 것으로 간주하지 않습니다. 그러나 단일 응용 프로그램이 아닌 일부 라이브러리 코드에서 사용하려는 경우 고려해야 할 사항이 있습니다.

  1. 값을 제거하거나 재정렬하지 마십시오. 어느 시점에서 목록에 열거 형 값이 있으면 그 값은 영원의 이름과 연결되어야합니다. 원하는 경우 어떤 시점에서 값의 이름을 바꿀 수 deprecated_orc있지만 값을 제거하지 않으면 이전 열거 세트에 대해 컴파일 된 이전 코드와의 호환성을보다 쉽게 ​​유지할 수 있습니다. 일부 새로운 코드가 오래된 열거 형 상수를 처리 할 수없는 경우 적절한 오류를 생성하거나 해당 값이 해당 코드에 도달하지 않도록하십시오.

  2. 그들에게 산술을하지 마십시오. 특히 주문 비교를하지 마십시오. 왜? 따라서 기존 값을 유지하지 못하고 동시에 올바른 순서를 유지하지 못하는 상황이 발생할 수 있습니다. 나침반 방향에 대한 열거 형을 예로 들어 보겠습니다. N = 0, NE = 1, E = 2, SE = 3, ... 이제 일부 업데이트 후에 기존 방향 사이에 NNE 등이 포함 된 경우 끝에 추가 할 수 있습니다. 목록의 순서를 바꾸어 순서를 어기거나 기존 키와 인터리브하여 레거시 코드에서 사용되는 기존 매핑을 깨뜨립니다. 또는 모든 이전 키를 폐기하고 레거시 코드를 위해 이전 키와 새 키를 변환하는 호환성 코드와 함께 완전히 새로운 키 세트를 갖습니다.

  3. 충분한 크기를 선택하십시오. 기본적으로 컴파일러는 모든 열거 형 값을 포함 할 수있는 가장 작은 정수를 사용합니다. 즉, 일부 업데이트에서 가능한 열거 집합이 254에서 259로 확장되면 1이 아닌 모든 열거 값마다 갑자기 2 바이트가 필요합니다. 이것은 모든 곳에서 구조와 클래스 레이아웃을 깨뜨릴 수 있으므로 첫 번째 디자인에서 충분한 크기를 사용하여 이것을 피하십시오. C ++ 11은 여기에서 많은 제어를 제공하지만 항목을 지정하면 LAST_SPECIES_VALUE=65535도움이됩니다.

  4. 중앙 레지스트리가 있어야합니다. 이름과 값 사이의 매핑을 수정하고 싶기 때문에 코드의 타사 사용자가 새로운 상수를 추가하게하면 좋지 않습니다. 코드를 사용하는 프로젝트는 새 매핑을 추가하기 위해 해당 헤더를 변경할 수 없습니다. 대신 그들은 당신을 추가하기 위해 당신을 버그해야합니다. 이것은 당신이 언급 한 인간과 난쟁이 예에서 열거 형이 실제로 적합하지 않다는 것을 의미합니다. 런타임에 일종의 레지스트리를 사용하는 것이 좋습니다. 여기서 코드 사용자는 문자열을 삽입하고 고유 한 숫자를 다시 얻을 수 있습니다. "숫자"는 해당 문자열에 대한 포인터 일 수도 있지만 중요하지 않습니다.

위의 마지막 요점은 열거 형이 가상의 상황에 적합하지 않다는 사실에도 불구하고 일부 사양이 변경되고 일부 코드를 업데이트 해야하는 실제 상황은 라이브러리의 중앙 레지스트리와 잘 어울리는 것 같습니다. 따라서 다른 제안을 마음에 새기면 열거 형이 적절해야합니다.

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