From e767822d5627e10dc8a955a832bc0167ba364906 Mon Sep 17 00:00:00 2001 From: Mellona Date: Tue, 5 Nov 2024 14:02:31 +0000 Subject: [PATCH] =?UTF-8?q?GITBOOK-181:=20item=2038=20:=20=ED=99=95?= =?UTF-8?q?=EC=9E=A5=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8A=94=20=EC=97=B4?= =?UTF-8?q?=EA=B1=B0=20=ED=83=80=EC=9E=85=EC=9D=B4=20=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=98=EB=A9=B4=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- developLog/SUMMARY.md | 1 + .../java/effective-java/6/item-38.md | 140 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 developLog/programming-lanuage/java/effective-java/6/item-38.md diff --git a/developLog/SUMMARY.md b/developLog/SUMMARY.md index 8ddd1c0..76f8a08 100644 --- a/developLog/SUMMARY.md +++ b/developLog/SUMMARY.md @@ -68,6 +68,7 @@ * [item 35 : ordinal 메서드 대신 인스턴스 필드를 사용하라](programming-lanuage/java/effective-java/6/item-35-ordinal.md) * [item 36 : 비트 필드 대신 EnumSet을 사용하라](programming-lanuage/java/effective-java/6/item-36-enumset.md) * [item 37 : ordinal 인덱싱 대신 EnumMap을 사용하라](programming-lanuage/java/effective-java/6/item-37-ordinal-enummap.md) + * [item 38 : 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라](programming-lanuage/java/effective-java/6/item-38.md) * [스터디에서 알아가는 것](programming-lanuage/java/effective-java/undefined.md) * [모던 자바 인 액션](programming-lanuage/java/modern-java-in-action/README.md) * [chaper 1. 자바 8, 9, 10, 11 : 무슨 일이 일어나고 있는가?](programming-lanuage/java/modern-java-in-action/chaper-1.-8-9-10-11.md) diff --git a/developLog/programming-lanuage/java/effective-java/6/item-38.md b/developLog/programming-lanuage/java/effective-java/6/item-38.md new file mode 100644 index 0000000..cf19041 --- /dev/null +++ b/developLog/programming-lanuage/java/effective-java/6/item-38.md @@ -0,0 +1,140 @@ +# item 38 : 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라 + +열거 타입을 확장 가능하게 만들고자 할 때, 자바에서는 `Enum` 자체의 확장은 허용되지 않는다. + +대신 **인터페이스**와 **열거 타입**을 함께 활용하여 열거 타입을 확장하는 것과 유사한 효과를 낼 수 있다. 이를 통해 열거 타입의 이점을 유지하면서도 필요한 경우 확장 가능한 열거 타입과 같은 기능을 구현할 수 있다. + +{% hint style="info" %} +확장 가능 열거 타입을 위한 인터페이스 사용 +{% endhint %} + +먼저, **인터페이스를 이용해 열거 타입의 기본 동작을 정의**한다. 그리고 열거 타입이 이 인터페이스를 구현하여 기본 기능을 제공하도록 설계한다. + +## 1. 연산 인터페이스 `Operation` + +기본적으로 제공하는 연산은 `Operation`이라는 인터페이스를 통해 정의된다. + +```java +public interface Operation { + double apply(double x, double y); +} +``` + +* `Operation` 인터페이스는 연산을 나타내는 추상 메서드 `apply(double x, double y)`를 제공한다, +* 이를 통해 연산을 수행하는 열거 타입이 추가될 때마다 `apply` 메서드를 각기 다른 방식으로 구현할 수 있게 된다. + +## 2. `BasicOperation` 열거 타입 + +기본 연산 기능을 제공하는 열거 타입 `BasicOperation`을 정의한다. 각 열거 상수는 `Operation` 인터페이스를 구현하여 연산을 수행한다. + +```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; + } +} +``` + +* `BasicOperation`은 `Operation` 인터페이스를 구현하며, `PLUS`, `MINUS`, `TIMES`, `DIVIDE`의 기본 연산을 정의한다. +* `apply` 메서드는 `Operation` 인터페이스의 메서드로서 열거 상수마다 다르게 정의된다. + +## 3. `ExtendedOperation` 열거 타입 + +기본 연산에 더해 새로운 연산을 정의하고자 할 때는 **또 다른 열거 타입을 정의**하여 `Operation` 인터페이스를 구현할 수 있다. + +```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; + } +} +``` + +* `ExtendedOperation`은 기존 연산 외에 추가 연산(거듭제곱(EXP)와 나머지(REMAINDER))을 구현한다. +* 기본 열거 타입 `BasicOperation`을 확장한 것은 아니지만, `Operation` 인터페이스를 구현함으로써 기존 연산과 같은 방식으로 사용할 수 있다. + +## 4. 확장 가능 열거 타입의 활용 예 + +**기존 연산과 새로운 연산을 모두 사용할 수 있도록** `Operation` 인터페이스를 기반으로 처리하는 메서드를 작성한한다. + +```java +public static void main(String[] args) { + double x = Double.parseDouble(args[0]); + double y = Double.parseDouble(args[1]); + + // BasicOperation과 ExtendedOperation 모두를 테스트하는 예제 + test(Arrays.asList(BasicOperation.values()), x, y); + test(Arrays.asList(ExtendedOperation.values()), x, y); +} + +private static void test(Collection opSet, double x, double y) { + for (Operation op : opSet) + System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y)); +} +``` + +* `test` 메서드는 `Collection` 타입을 받아, `Operation` 인터페이스를 구현한 열거 타입을 모두 처리할 수 있다. +* `test(Arrays.asList(BasicOperation.values()), x, y);`는 `BasicOperation`의 연산을 테스트하고, `test(Arrays.asList(ExtendedOperation.values()), x, y);`는 `ExtendedOperation`의 연산을 테스트한다. + +## 5. `Class`를 이용한 확장성 제공 + +연산 인터페이스와 열거 타입을 `Class`로 받아 **확장된 열거 타입의 모든 원소를 순회하는 방식**으로도 사용 가능하하다. + +```java +public static & Operation> void test(Class 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 opEnumType`에 `ExtendedOperation.class`를 넘기면 확장된 연산을 포함해 열거 타입의 모든 원소를 사용할 수 있습. +* ` & Operation>`으로 제약을 설정하여 `Enum`이면서 `Operation`을 구현한 타입만 허용한다. + +### 요약 + +이 패턴을 사용하면 **기존 열거 타입을 수정하지 않고도 확장된 연산을 추가**할 수 있다. `Operation` 인터페이스를 통해 **확장 가능성을 제공하면서도 열거 타입의 장점**을 유지하는 이점이 있다. + +1. **타입 안전성**을 유지하며 새로운 연산을 추가할 수 있다. +2. **코드 중복**을 줄이고, 기본 열거 타입과 확장된 열거 타입의 사용 방식을 동일하게 유지한다. +3. **EnumMap**이나 **EnumSet**을 사용할 수는 없지만, `Operation` 인터페이스를 사용해 **기존 코드를 그대로 확장**할 수 있다. + +이 패턴은 주로 `java.nio.file.LinkOption` 등이 적용된 방식으로, **인터페이스와 열거 타입을 결합**하여 확장 가능한 열거 타입을 구현하는 효과를 제공한다. + +## 핵심 정리 + +> 열거 타입 자체는 확장할 수 없지만, 인터페이스와 그 인터페이스를 구현하는 기본 열거 타입을 함께 사용해 같은 `효과`를 낼 수 있다. + +* 이렇게 하면 클라이언트는 이 인터페이스 를 구현해 자신만의 열거 타입혹은 다른 타입을 만들 수 있다. +* API가 기본 열거 타입을 직접 명시하지 않고 **인터페이스 기반으로 작성되었다면** 기본 열거 타입의 인스 턴스가 쓰이는 모든 곳을 **새로 확장한 열거 타입의 인스턴스로 대체해 사용할 수 있다.**