Skip to content

Latest commit

 

History

History
126 lines (100 loc) · 4.04 KB

확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라.md

File metadata and controls

126 lines (100 loc) · 4.04 KB

부제 : extends 해야하는 열거 타입이 필요하면 인터페이스를 사용하라

열거 타입은 확장(extends)할 수 없다. 확장이 불가능하도록 설계한 이유는 아래와 같다.

  • 확장한 타입의 원소는 기반 타입의 원소로 취급하지만 그 반대가 성립하지 않다면 이상하다.
  • 기반 타입과 확장된 타입들을 한번에 순회할 방법이 없다.
  • 확장성을 높이려면 고려할 요소가 늘어나 설계와 구현이 복잡해진다.

따라서 확장할 수 있는 열거 타입이 필요하다면 인터페이스를 사용하자.

그 예시가 바로 아래와 같은 연산 코드(opcode)이다.

public interface Operation {
    double operate(final double x, final double y);
}
public enum BasicOperation implements Operation {
    PLUS("+") {
        @Override
        public double operate(final double x, final double y) {
            return x + y;
        }
    }, MINUS("-") {
        @Override
        public double operate(final double x, final double y) {
            return x - y;
        }
    }, TIMES("") {
        @Override
        public double operate(final double x, final double y) {
            return x - y;
        }
    }, DIVIDE("/") {
        @Override
        public double operate(final double x, final double y) {
            return x - y;
        }
    },
    ;

    private final String symbol;

    BasicOperation(final String symbol) {
        this.symbol = symbol;
    }
}

위와 같은 식으로 Operation을 확장할 수 있다. 또한 기본 4칙 연산 외의 다른 Operation이 필요하다면, 해당 객체에 추가해도 되고 아래와 같이 새로운 열거 타입을 만들어도 된다.

public enum ExtendedOperation implements Operation {
    EXP("^") {
        @Override
        public double operate(final double x, final double y) {
            return Math.pow(x, y);
        }
    },
    REMAINDER("%") {
        @Override
        public double operate(final double x, final double y) {
            return x % y;
        }
    }
    ;

    private final String symbol;
    
    ExtendedOperation(final String symbol) {
        this.symbol = symbol;
    }
}

타입 수준에서도 기본 열거 타입 대신 확장된 열거 타입을 넘겨 확장된 열거 타입의 원소 모두를 사용할 수 있다.

1. class 리터럴 넘기기

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(ExtendedOperation.class, x, y);
}

private static <T extends Enum<T> & Operation> void test(Class<T> opEnumType, double x, double y) {
    for (Operation operation : opEnumType.getEnumConstants()) {
        System.out.printf("%f %s %f = %f%n", x, operation, y, operation.apply(x, y));
    }
}

한정적 타입 토큰 class 리터럴을 넘겨 연산이 무엇이 알려준다.

Class의 getEnumConstants() 메소드로 모든 열거 타입을 가진 배열을 받을 수 있다.

2. 한정적 와일드카드 타입에 모든 열거 값들 넘기기

public static void main(String[] args) {
    double x = Double.parseDouble(args[0]);
    double y = Double.parseDouble(args[1]);
    test(Arrays.asList(ExtendedOperation.values()), x, y);
}

private static void test(Collection<? extends Operation> operations, double x, double y) {
    for (Operation operation : operations) {
        System.out.printf("%f %s %f = %f%n", x, operation, y, operation.apply(x, y));
    }
}

덜 복잡해고 메소드가 보다 유연해졌다. 하지만 특정 연산에서 EnumSet과 EnumMap을 사용할 수 없다.

문제점

  • 열거 타입끼리 상속할 수 없다. (ExtendedOperation이 BasicOperation을 extends할 수 없다.)
  • 연산 기호를 저장하고 찾는 로직이 BasicOperation과 ExtendedOperation 모두에 들어가야해서 복잡해진다.(중복량이 많아져 코드가 복잡해진다면)

해결 방안

  • 부가적인 클래스 혹은 정적 메소드로 분리하는 방식으로 코드의 중복을 없앨 수 있다.