peaches-book-study / effective-java

이펙티브 자바 3/E
0 stars 2 forks source link

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

Open pyeong114 opened 3 months ago

pyeong114 commented 3 months ago

Chapter : 6. 열거 타입과 애너테이션

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

Assignee : pyeong114


🍑 서론

열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하지만, 확장가능성에서 예외가 있다.

타입 안전 열거 패턴은 열거한 값들을 그대로 가져온 다음 값을 더 추가하여 다른 목적으로 쓸 수 있는 반면, 열거 타입은 그렇게 할 수 없다는 의미이다.

사실 대부분 상황에서 열거 타입을 확장하는 것은 좋은 생각은 아니지만, 딱 하나 적절한 부분이 있는데 그건 바로 연산 코드이다.

연산 코드의 각 원소는 특정 기계가 수행하는 연산을 의미

열거타입

: 변수가 특정 값들 중 하나만 가질 수 있도록 제한하는 데이터 타입

public enum Day {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;
}

public class Test {
    public static void main(String[] args) {
        Day day = Day.MONDAY;

        switch(day) {
            case MONDAY:
                System.out.println("월요일입니다.");
                break;
            case FRIDAY:
                System.out.println("금요일입니다.");
                break;
            default:
                System.out.println("월요일도 금요일도 아닙니다.");
        }
    }
}

타입 안전 열거타입

: 열거 타입을 지원하지 않거나, 메소드 추가를 통해 열거 타입의 기능을 확장하고 싶을 때 사용 : 객체 지향 방식으로 열거 타입을 구현할 수 있으며, 각 열거 값이 싱글턴 인스턴스로 관리되어 타입 안전성 보장

public class Day {
    private final String name;

    private Day(String name) {
        this.name = name;
    }

    public static final Day MONDAY = new Day("MONDAY");
    public static final Day TUESDAY = new Day("TUESDAY");
    public static final Day WEDNESDAY = new Day("WEDNESDAY");
    public static final Day THURSDAY = new Day("THURSDAY");
    public static final Day FRIDAY = new Day("FRIDAY");
    public static final Day SATURDAY = new Day("SATURDAY");
    public static final Day SUNDAY = new Day("SUNDAY");

    public String toString() {
        return this.name;
    }
}

public class Test {
    public static void main(String[] args) {
        Day day = Day.MONDAY;
        System.out.println(day);

        if (day == Day.MONDAY) {
            System.out.println("오늘은 월요일입니다.");
        } else {
            System.out.println("오늘은 월요일이 아닙니다.");
        }
    }
}

🍑 본론

열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용해서 확장하면 된다.

연산 코드용 인터페이스를 정의하고 열거 타입이 이 인터페이스를 구현하게 하면 된다. 이때 열거 타입이 그 인터페이스의 표준 구현체 역할을 한다.

public interface Operation{
     double apply(double x, double y);
}
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;
    }
}

열거 타입인 BasicOperation은 확장할 수 없지만, 인터페이스인 Operation은 확장할 수 있고, 이 인터페이스를 연산의 타입으로 사용하면 된다.

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

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;
    }
}

Operation 인터페이스를 사용하도록 작성되어 있기만 하면, 새로 작성한 연산은 기존 연산을 사용하던 곳 어디든 쓸 수 있다.

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

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

// test(ExtendedOperation.class, 4, 2)
// 4.000000 ^ 2.000000 = 16.000000 
// 4.000000%2.000000= 0.000000

<T extends Enum<T> & Operation> : opEnumType 매개변수의 선언 : Class 객체가 열거 타입인 동시에 Operation의 하위 타입이어야 한다는 뜻

🍑 결론

열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 효과를 낼 수 있다.


Referenced by

-