Yeom's
Yeom's Coding Note
Yeom's
전체 방문자
오늘
어제
  • 분류 전체보기 (262)
    • 백준온라인 (94)
    • 운영체제 (14)
    • 프로그래머스 (0)
    • Go Language (40)
    • AWS (1)
    • 자료구조 (21)
    • Effective Java 3E (41)
    • JPA (1)
    • Tech Interview (33)
    • MySQL 8.0 (7)
    • CS 기술면접 (7)
    • Linux (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Real MySQL 8.0
  • https://mangkyu.tistory.com/93
  • boj 2839
  • RealMySQL 8.0
  • https://mangkyu.tistory.com/94
  • java
  • https://mangkyu.tistory.com/90
  • UTM
  • 출처 : 망나니 개발자
  • maeil-mail
  • 망나니 개발자
  • m1 linux
  • https://mangkyu.tistory.com/91
  • 백준 설탕 배달
  • https://mangkyu.tistory.com/92
  • https://mangkyu.tistory.com/88
  • https://mangkyu.tistory.com/89
  • 망나니개발자
  • 교보dts

최근 글

티스토리

hELLO · Designed By 정상우.
Yeom's

Yeom's Coding Note

Effective Java 3E

학습기록 - 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

2023. 5. 9. 15:00
728x90
반응형

열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴(typesafe enum pattern)보다 우수하다. 단, 예외가 하나 있으니, 타입 안전 열거 패턴은 확장할 수 있으나 열거 타입은 그럴 수 없다는 점이다. 달리 말하면, 타입 안전 열거 패턴은 열거한 값들을 그대로 가져온 다음 값을 더 추가하여 다른 목적으로 쓸 수 있는 반면, 열거 타입은 그렇게 할 수 없다는 뜻이다. 실수로 이렇게 설계한 것은 아니다. 사실 대부분 상황에서 열거 타입을 확장하는 건 좋지 않은 생각이다. 확장한 타입의 원소는 기반 타입의 원소로 취급하지만 그 반대는 성립하지 않는다면 이상하지 않은가! 기반 타입과 확장된 타입들의 원소 모두를 순회할 방법도 마땅치 않다. 마지막으로 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 더 복잡해진다.

 

그런데 확장할 수 있는 열거 타입이 어울리는 쓰임이 최소한 하나는 있다. 바로 연산 코드(operation code 혹은 opcode)이다. 연산 코드의 각 원소는 특정 기계가 수행하는 연산을 뜻한다. 이따금 API가 제공하는 기본 연산 외에 사용자 확장 연산을 추가할 수 있도록 열어줘야 할 때가 있다.

 

다행히 열거 타입으로 이 효과를 내는 멋진 방법이 있다. 기본 아이디어는 열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용하는 것이다. 연산 코드용 인터페이스를 정의하고 열거 타입이 이 인터페이스를 구현하게 하면 된다. 이때 열거 타입이 그 인터페이스의 표준 구현체 역할을 한다. 다음은 Operation 타입을 확장할 수 있게 만든 코드다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public enum BasicOperation implements Operation{
    PLUS("+"){
        public double apply(double x, double y) { return x+y; }
    },
    MINUS("-"){
        public double apply(double x, double y) { return x-y; }
    },
    TIMES("*"){
        public double apply(double x, double y) { return x*y; }
    },
    DIVIDE("/"){
        public double apply(double x, double y) { return x/y; }
    };
 
    private final String symbol;
 
    BasicOperation(String symbol){
        this.symbol = symbol;
    }
 
    @Override public String toString(){
        return symbol;
    }
}
Colored by Color Scripter
cs

열거 타입인 BasicOperation은 확장할 수 없지만 인터페이스인 Operation은 확장할 수 있고, 이 인터페이스를 연산의 타입으로 사용하면 된다. 이렇게 하면 Operation을 구현한 또 다른 열거 타입을 정의해 기본 타입인 BasicOperation을 대체할 수 있다. 예를 들어 앞의 연산 타입을 확장해 지수 연산(EXP)과 나머지 연산(REMAINDER)을 추가해보자. 이를 위해 우리가 할 일은 Operation 인터페이스를 구현한 열거 타입을 작성하는 것뿐이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public enum ExtendedOperation implements Operation{
    EXP("^"){
        public double apply(double x, double y){ return Math.pow(x,y) ; }
    },
    REMAINDER("%"){
        public double apply(double x, double y){ return x%y ; }
    };
 
    private final String symbol;
    
    ExtendedOperation(String symbol){
        this.symbol = symbol;
    }
 
    @Override public String toString(){
        return symbol;
    }
}
Colored by Color Scripter
cs

이 코드는 그나마 덜 복잡하고 test 메서드가 살짝 더 유연해졌다. 다시 말해 여러 구현 타입의 연산을 조합해 호출할 수 있게 되었다. 반면, 특정 연산에서는 EnumSet과 EnumMap을 사용하지 못한다. 

 

두 대안 프로그램 모두 명령줄 인수로 4와 2를 넣어 실행하면 다음 결과를 출력한다. 

 

4.000000 ^ 2.000000 = 16.000000

4.000000 % 2.000000 = 0.000000

 

인터페이스를 이용해 확장 가능한 열거 타입을 흉내 내는 방식에도 한 가지 사소한 문제가 있다. 바로 열거 타입끼리 구현을 상속할 수 없다는 점이다. 아무 상태에도 의존하지 않는 경우에는 디폴트 구현을 이용해 인터페이스에 추가하는 방법이 있다. 반면 Operation 예는 연산 기호를 저장하고 찾는 로직이 BasicOperation과 ExtendedOperation 모두에 들어가야만 한다. 이 경우에는 중복량이 적으니 문제 되지 않지만, 공유하는 기능이 많다면 그 부분을 별도의 도우미 클래스나 정적 도우미 메서드로 분리하는 방식으로 코드 중복을 없앨 수 있을 것이다.

 

자바 라이브러리도 이번 아이템에서 소개한 패턴을 사용한다. 그 예로 java.nio.file.LinkOption 열거 타입은 CopyOption과 OpenOption 인터페이스를 구현했다.

 

핵심 정리

"열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다. 이렇게 하면 클라이언트는 이 인터페이스를 구현해 자신만의 열거 타입(혹은 다른 타입)을 만들 수 있다. 그리고 API가 (기본 열거 타입을 직접 명시하지 않고) 인터페이스 기반으로 작성되었다면 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다."

728x90
반응형

'Effective Java 3E' 카테고리의 다른 글

학습기록 - @Override 어노테이션을 일관되게 사용하라  (0) 2023.05.09
학습기록 - 명명 패턴보다 애너테이션을 사용하라  (0) 2023.05.09
학습기록 - ordinal 인덱싱 대신 EnumMap을 사용하라  (0) 2023.05.08
학습기록 - 비트 필드 대신 EnumSet을 사용하라  (0) 2023.05.08
학습기록 - ordinal 메서드 대신 인스턴스 필드를 사용하라  (0) 2023.05.07
    'Effective Java 3E' 카테고리의 다른 글
    • 학습기록 - @Override 어노테이션을 일관되게 사용하라
    • 학습기록 - 명명 패턴보다 애너테이션을 사용하라
    • 학습기록 - ordinal 인덱싱 대신 EnumMap을 사용하라
    • 학습기록 - 비트 필드 대신 EnumSet을 사용하라
    Yeom's
    Yeom's

    티스토리툴바