[Java] Item 38 : 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
Effective Java를 읽고 정리한 정리본입니다.
📌 Item 38 : 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
🫧 타입 안전 열거 패턴
이름처럼, 타입 안전을 보장하는 열거 패턴을 말한다.
enum 클래스가 등장하기 전 사용된 패턴이며, 해당 내용에 대한 예시는 다음과 같다.
public class ClassGrade {
public static final ClassGrade BASIC = new ClassGrade();
public static final ClassGrade GOLD = new ClassGrade();
public static final ClassGrade DIAMOND = new ClassGrade();
private ClassGrade() {} // 또 다른 인스턴스가 생성되는 것을 방지하기 위해 생성자를 private으로 선언 (외부 접근 차단)
}
관련 클래스를 만들고 열거할 상수를 선언한다.
이때 싱글턴 패턴처럼 상수마다 별도의 인스턴스를 만들고 (new ClassGrade()) 불변 객체로 만들기 위해 static, final을 사용해 타입 안전을 보장한다.
그러나 이 패턴을 구현하려면 쓸모 없는 코드가 많아지며, private 생성자를 추가하는 등 유의해야 하는 부분들 또한 늘어난다.
이러한 문제를 방지하기 위해 나온 것이 열거 타입이다.
🫧 열거 타입
열거 타입은 거의 모든 상황에서 타입 안전 열거 패턴보다 우수하다.
패턴 구현에 필요한 코드들이 내부 동작으로 숨겨져 있으며, 쉽게 변경이 가능하기 때문이다.
그러나 예외가 하나 존재한다.
타입 안전 열거 패턴은 확장이 가능하나, 열거 타입은 그럴 수 없다는 점이다.
확장성을 높이는 것보다 안전을 채택한 것이 그 이유였다.
그러나 연산 코드와 같은 예시에서는 이따금 API가 제공하는 기본 연산 외에도 사용자 확장 연산을 추가할 수 있도록 열여줘야 한다.
이러한 방식은 “인터페이스”를 활용해 구현이 가능하다.
✨ 인터페이스를 활용한 열거 타입 구현
연산 코드용 인터페이스를 정의하고 열거 타입이 이 인터페이스를 구현하게 하면 되는 것이다.
이때 열거 타입은 그 인터페이스의 표준 구현체 역할을 한다.
public interface Operation {
double apply(double xx, 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;
}
}
이 연산에 지수 연산 (EXP)과 나머지 연산 (REMAINDER)을 추가해보자.
public enum ExtendedOperation implements Operation {
EXP("^") {
public double apply(double x, double y) { return 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 인터페이스를 구현하였으므로, BasicOperation이 아닌 Operation 인터페이스를 사용하도록 작성된 모든 코드에서 사용할 수 있다.
그러나 열거 타입끼리 구현을 상속할 수 없다는 점이 단점으로 작용한다.
아무 상태에도 의존하지 않는다면 디폴트 구현을 이용해 인터페이스에 추가할 수도 있다.
그러나 계속해서 추가함으로써 생성자, toString 오버라이딩 등 중복 코드들이 증가할 수 있다.
이럴 때는 별도의 도우미 클래스나 정적 도우미 메서드로 분리함으로써 이러한 문제를 해결할 수 있다.
실제 자바 라이브러리에서도 java.nio.file.LinkOption 열거 타입은 CopyOption과 OpenOption 인터페이스를 구현한 예시를 찾을 수 있다.
🫧 참고 자료
[Java] 타입 안전 열거형 패턴 (Type-Safe-Enum Pattern) | Tistory | 쌈뽕코딩