Item 22
인터페이스 타입을 정의하는 용도로만 사용하라
🟧 인터페이스는 타입을 정의한다.
인터페이스를 사용하면 메서드, 필드 등을 선언해야 한다.
인터페이스를 구현하는 클래스나 객체가 반드시 가져야하는 요구사항(시그니처 및 타입)을 명시해야 한다.
이러한 용도로서 인터페이스는 타입을 정의한다 할 수 있다.
아래는
interface Move,
class Piece 가 있다.
Piece는 Move의 구현체로 move() 메서드의 시그니처를 가지고 메서드를 구현한다.
@DisplayName("인터페이스는 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다.")
@Test
void interface_reference() {
// Move를 구현한 Piece를 참조할 수 있다.
final Move move = new Piece();
}
interface Move {
void move();
}
class Piece implements Move {
@Override
public void move() {
}
}
🟧 인터페이스를 구현한 객체들은 서로 다른 구현 방식을 가질 수 있다.
인터페이스를 구현한 객체들은 메서드의 시그니처는 같지만 내부 동작은 다를 수 있다.
아래는
interface Move와
Move의 구현체인 King, Queen, Bishop, Rook이 있다.
서로 같은 void move() 시그니처를 가지고 있지만
실제 내부 동작은 다른 구현 방식을 가지고 있다.
@DisplayName("인터페이스를 구현한 클래스는 서로 다른 구현 방식을 가질 수 있다.")
@Test
void interface_implements() {
// 왕처럼 움직이거라.
final Move kingMove = new King();
// 퀸처럼 움직이거라.
final Move queenMove = new Queen();
// 비숍처럼 움직이거라.
final Move bishopMove = new Bishop();
// 룩처럼 움직이거라.
final Move rookMove = new Rook();
}
class King implements Move {
@Override
public void move() {
System.out.println("왕처럼 움직이거라.");
}
}
class Queen implements Move {
@Override
public void move() {
System.out.println("퀸처럼 움직이거라.");
}
}
class Bishop implements Move {
@Override
public void move() {
System.out.println("비숍처럼 움직이거라.");
}
}
class Rook implements Move {
@Override
public void move() {
System.out.println("룩처럼 움직이거라");
}
}
🟧 상수 인터페이스 안티 패턴
인터페이스에 상수 인터페이스를 선언할 수 있다.
인터페이스에 상수 인터페이스를 선언하면 여러 단점이 있을 수 있다.
- 사용하지 않을 상수를 구현한 모든 클래스에서 알고 있게 된다.
- 내부 구현을 클래스의 API로 노출한다.
- 어떤 상수 인터페이스를 사용할 수 있어서 혼란을 줄 수 있다.
@DisplayName("상수 인터페이스는 내부 구현을 노출하는 것이다.")
@Test
void interface_anti() {
// 비숍은 대각선으로만 움직일 수 있다.
final Move bishopMove = new Bishop();
// interface Move 내부 상수를 알아야 한다.
// 다른 곳에서도 움직임에 대한 값이 필요할 때 interface Move에 접근해야 한다.
bishopMove.move(Move.DLF);
}
interface Move {
String F = "앞";
String L = "왼쪽";
String R = "오른쪽";
String B = "뒤";
String DFL = "앞쪽 대간선 왼편";
String DFR = "앞쪽 대각선 오른편";
String DBL = "뒤쪽 대각선 왼편";
String DBR = "뒤쪽 대각선 오른편";
void move(final String movement);
}
class Bishop implements Move {
@Override
public void move(final String movement) {
System.out.printf("비숍처럼 움직이거라. => %s", movement);
}
}
🟧 상수를 제공하려면 Enum, 유틸리티 클래스를 이용하자.
상수 인터페이스를 이용하면 이후에 해당 인터페이스 자체가 필요하지 않더라도
상수 인터페이스를 외부에서 사용하고 있으면 삭제할 수 없다.
이러한 문제점을 해결하기 위한 간단한 방법은 두 가지가 있다.
첫 번째, Enum을 이용한다. [ITEM 34]
enum MOVEMENT {
F("앞"),
L("왼쪽"),
R("오른쪽"),
B("뒤"),
DFL("앞쪽 대간선 왼편"),
DFR("앞쪽 대각선 오른편"),
DBL("뒤쪽 대각선 왼편"),
DBR("뒤쪽 대각선 오른편");
private final String direction;
MOVEMENT(final String direction) {
this.direction = direction;
}
public String getDirection() {
return direction;
}
}
두 번째, 유틸리티 클래스를 이용한다. [ITEM 4]
public static class MovementUtil {
private MovementUtil() {
}
public static final String F = "앞";
public static final String L = "왼쪽";
public static final String R = "오른쪽";
public static final String B = "뒤";
public static final String DFL = "앞쪽 대간선 오른편";
public static final String DFR = "앞쪽 대각선 오른편";
public static final String DBL = "뒤쪽 대각선 왼편";
public static final String DBR = "뒤쪽 대각선 오른편";
}
🟧 정리하자면
상수 인터페이스를 이용하면 내부 구현을 외부에 노출하는 것이다.
이후에 인터페이스 자체가 사용하지 않는다고 하더라고
상수 인터페이스를 외부에서 사용하고 있다면 인터페이스를 삭제하기 쉽지 않을 것이다.
그러니 상수 같은 경우 Enum이나 유틸리티 클래스를 이용하자.
'💬 언어 > 이펙티브 자바' 카테고리의 다른 글
[이펙티브 자바] Item 23 - 태그 달린 클래스보다는 클래스 계층구조를 활용하라 (0) | 2023.04.14 |
---|---|
[이펙티브 자바] Item 20 - 추상 클래스보다는 인터페이스를 우선하라 (0) | 2023.03.13 |
[이펙티브 자바] Item 49 - 매개변수가 유효한지 검사하라 (0) | 2023.03.06 |
[이펙티브 자바] Item 32 - 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2023.03.05 |
[이펙티브 자바] Item 26 - Raw Type은 사용하지 말라 (0) | 2023.02.22 |