typesafe enum

http://blog.hanmir.com/sedere/282388

음… 뭐 제가 언어 설계자도 아니지만 effective JAVA를 쓴 조슈아가 아마 이 typesafe enum의 presenter였었죠. (그리고 JDK 1.5의 enum 도 이 사람이 만든 듯.)

그런이유로 그 책의 내용을 인용하자면, 애초에 typesafe enum이란 패턴을 만든 이유는 다음과 같습니다.

(아마 그 C# 아키텍트 아저씨는 이 책 안보신 듯. 책의 내용을 반박하면 흥미있겠지만, Am I missing here? 라고만 간략히 하시면 absolutely. you didn’t get what Joshua said…. -_-;;)

1. C의 enum은 다음과 같은 이상한 코드가 가능하다.


typedef enum {FUJI, PIPPIN, GANNY_SMITH } apple_t;
typedef enum {NAVEL, TEMPLE, BLOOD} orange_t;

orange_t myFavorite = PIPPIN;

보다시피 오렌지에 사과를 할당할 수 있는 문제가 있습니다.

2. 인터페이스를 사용한 상수선언은 문제가 많다.


public interface XXXConstant {

  public static final int XXX_XXX=0;
  public static final int XXX_XXX=1;
  ...
}

이름에서 유추하다시피 Constant를 위한 특별한 인터페이스가 선언되어있고, 이러한 구현은 결국 인터페이스에 상수를 publish 해서 발생하는 단점들이 있다.. 고 복잡한 얘기들이 있으나 생략하고,

어쨌든 이러한 코드를 가지고


public class MyClass implements XXXConstants {
...
}

와 같이 implements 키워드를 써서 손쉽게 상수를 접근 (XXXConstants.XXX_XXXX 대신 XXX_XXXX 형태로 사용)하는 사람들이 많았으나 사실 MyClass가 상수의 구현체는 아니므로 이런 것이 잘못되었다는 거고, 그것이 static import의 도입 이유입니다.

그리고 또다른 문제는 이런 형태로 된 코드는 상수 값이 그대로 클라이언트 코드 (이 상수를 사용하는 클래스)에 embedded 되어 컴파일됩니다. 즉, 숫자값이 그대로 박혀버림.

따라서 상수를 변형하면 클라이언트를 재컴파일(숫자 값을 바꾸거나, 상수의 선언 순서를 바꾸거나 했을 때) 해야합니다.

클래스였다면 런타임이니까, 박혀버릴 수가 없죠.

3. 상수 값을 프린트해서 보기가 불편하다.

이 점은 클래스로 구현하고 ToString()이든 toString()이든 오버라이딩하면 되리라는 대강의 예상이 되죠..

근데 이거 프린트하기 편하라고


public static final String XXX_CONST="..."

이랬던 사람들도 있었다나봅니다. (진짜 세상 오래살고 봐야되요. 머리도 좋아..)

프린트야 잘되겠지만 error-prone 하고, 문자열은 equals 써서 비교해야되죠. 당연 속도저하.

4. 그리고 프린트 되는 문장을 국제화시키는 것은 클래스로 만들어야 가능하겠죠?

5. 클래스로 되어있는 enum은 인터페이스를 구현해서 확장이 가능하고

6. 자식 클래스도 만들 수 있겠죠.

7. 다 좋은데 문제는 뭐냐면 이렇게 클래스화 시켜서 작성하려면,
7.1. serializable 구현시 아주 잘해줘야하고 (안그러면 악의 적인 코드를 사용하면 같은 상수 2개 생겨버림)
7.2. 상속을 지원하려면 오버라이딩을 적절히 막아줘야하고 (equals 등)
7.3. 이런 일들이 드럽게 복잡하죠.

8. 그리고 클래스로 되어있으면 당연히 switch 문에서 못쓰죠.

그래서 대략 이러한 이유들로 잡일을 컴파일러가 해주면 편하겠다.. 해서 enum 이 클래스가 된 것입니다….

근데, 뭐 … 말했다시피 전 이렇게 더럽게 복잡한거 별로 안좋아하죠.

공부하기 귀찮으니까 -_-;;;

말이 나왔으니말이지, vendor framework lock-in 이라고… 이건 뭐 버젼업 한번했더니 봐야되는게 뭐 이렇게 많은건지 모르겠군여…

다만, 언어가 복잡해질때의 장점은 진입장벽이 높아져서 그 장벽뒤에 잘 숨으면 뭐 나름대로 살기 편해지는 면도 많다는거라던가, 좋은 칼을 잘 쓰면 좋다는 점 같네요…

대강 typesafe enum 패턴을 쓰면


public class SimpleType {

    private final String fName;

    private SimpleType(String name) {
        fName = name;
    }

    public static final XXX_CONST = new SimpleType("...");
    public static final XXX_CONST = new SimpleType("...");

    public String toString() { return fName; }
}

이런식이죠..

name은 final 이니까 변경안되고, 생성자 private이어서 외부 생성안되고, toString()으로 볼 수 있고, static final 이니까 equals가 아니라 = 로 비교가능해요.

하지만 문제는 뭐냐면 상속에서 발생하는데요.
이거 그냥 메모리로 비교 하려면 (Object.equals() 는 메모리 주소가 같은 지를 비교하는데 자식에서 내용 비교로 바꿔버리지 못하게 하려면) 자식들에서 equals를 내용 비교로 오버라이딩하면 안되니까 equals를 final 로 오버라이딩해서 Object.equals 부르게 해놓고, equals 바꿨으니까 당연히 hashCode() 역시 final로 Object.hashCode() 부르게 해야겠죠.
그리고 외부에서 임의로 수정한 클래스 (확장자 class)파일을 사용해서 공격 못하게 readResolve 잘 막아놔야하죠..

Similar Posts:

Post a Comment

Your email is never published nor shared.