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

## 열거(enum)타입이란?

고정된 상수들의 집합을 표현하는 특별한 종류의 클래스

### 열거타입의 특징

* 고정된 상수 집합을 표현할 때 사용한다
* 다른 클래스를 상속할 수 없다
* 일반적으로 열거 타입을 확장하는 것은 좋지 않다

<br>

## 열거타입을 확장하려면? 인터페이스를 이용하자!

### 예시

#### 공통 기능을 담은 인터페이스

```java
public interface Operation {
    double apply(double x, double y);
}
```

#### 기본 연산 기능을 담은 열거 타입

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

#### 확장 연산 기능을 담은 열거 타입

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

### 사용 방법

#### 📌 방법 1 : 클래스 타입 전달 (한정적 타입 매개변수 사용)

```java
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 op : opEnumType.getEnumConstants())
        System.out.printf("%f %s %f = %f%n",
                x, op, y, op.apply(x, y));
}
```

✔️ 특징

* **클래스 타입 (Class)** 을 전달해야 한다
* 전달받은 클래스에서 enum 상수 전체를 자동으로 얻을 수 있다
* 타입 안전하고, enum을 기반으로 반복할 때 유리하다

<br>

✅ 장점

* enum 클래스 하나를 통째로 테스트할 때 편리하다
* Enum.getEnumConstants()를 통해 열거 상수를 자동으로 얻을 수 있다

<br>

❌ 단점

* 반드시 한 enum 클래스 전체를 테스트해야 한다
* EarthOperation과 MarsOperation을 같이 넣을 수 없다

<br>

#### 📌 방법 2 : 컬렉션 전달 (한정적 와일드카드 사용)

```java
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> opSet,
                            double x, double y) {
    for (Operation op : opSet)
        System.out.printf("%f %s %f = %f%n",
                x, op, y, op.apply(x, y));
}
```

✔️ 특징

* Set이나 List 같은 컬렉션을 직접 전달한다
* Operation 인터페이스를 구현한 상수라면 무엇이든 섞어서 전달 가능하다

<br>

✅ 장점

* 서로 다른 enum 타입의 상수를 섞어서 테스트할 수 있다\
  (예: BasicOperation + ExtendedOperation)
* 사용할 연산만 선택적으로 골라서 넘길 수 있다

<br>

❌ 단점

* enum 클래스처럼 자동으로 상수를 가져올 수는 없다\
  ➔ 직접 Set.of(...) 같은 방식으로 수동으로 넘겨야 한다
* 다른 enum 의 상수에 대해선 EnumSet, EnumMap 이 제한된다.

  ```java
  Set<MarsOperation> s = EnumSet.of(MarsOperation.MARS_PLUS, MarsOperation.MARS_MINUS); // 가능

  Set<Operation> s = EnumSet.of(EarthOperation.MINUS, EarthOperation.PLUS); // 불가능

  Set<Operation> s = EnumSet.of(EarthOperation.MINUS, MarsOperation.MARS_PLUS); // 불가능
  ```

<br>

### 자바 라이브러리의 예시

```java
public enum LinkOption implements OpenOption, CopyOption {
    /**
     * Do not follow symbolic links.
     *
     * @see Files#getFileAttributeView(Path,Class,LinkOption[])
     * @see Files#copy
     * @see SecureDirectoryStream#newByteChannel
     */
    NOFOLLOW_LINKS;
}

```

### ❗️ 주의!

1. 열거 타입끼리는 상속이 불가능하다
2. 구현 코드가 중복될 수 있다 (각 열거 타입이 비슷한 코드를 가질 수 있음)
3. 코드 중복이 많아지면 도우미 클래스나 정적 메서드로 분리하는 것이 좋다

<br>

***

### 🧩 어려웠던 점

* 처음에는 열거 타입의 제약(상속 불가)이 왜 있는지 이해하기 어려웠다
* 인터페이스를 통한 확장이라는 개념이 처음에는 복잡해 보였다

<br>

### 💭 느낀 점

* 같은 연산 인터페이스를 구현하면, 여러 enum을 조합해서 쓸 수 있다는 점이 아주 유용하다고 느꼈다
* 가끔은 제약이 있는 것이 더 안전한 코드를 작성하는 데 도움이 된다는 것을 느꼈다
