[Why]
- 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다.
자바는 단일 상속만 지원하니, 추상 클래스 방식은 새로운 타입을 정의하는 데 커다란 제약을 안게 되는 셈이다. - 인터페이스가 선언한 메서드를 모두 정의하고 일반 규약을 잘 지킨 클래스라면 다른 어떤 클래스를 상속했든 같은 타입으로 취급된다.
- 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있다. 클래스 선언에 implements 구문만 추가하면 된다.
- 기존 클래스 위에 새로운 추상 클래스를 끼워넣기는 일반적으로 어렵다.
- 인터페이스는 믹스인(mixin) 정의에 안성맞춤이다.
- 인터페이스로는 계층구조가 없는 타입 프레임워크를 만들 수 있다.
- e.g.) Singer 인터페이스, Songwriter 인터페이스, SingerSongwriter 인터페이스
- 같은 구조를 클래스로 만들려면 가능한 조합 전부를 각각의 클래스로 정의한 고도비만 계층구조가 만들어질 수 있다.
조합 폭발(combinatorial explosion)
※ 믹스인(mixin) : 클래스가 구현할 수 있는 타입으로, 믹스인을 구현한 클래스에 원래의 '주된 타입' 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다. 예컨대 Comparable은 자신을 구현한 클래스의 인스턴스들끼리는 순서를 정할 수 있다고 선언하는 믹스인 인터페이스다. 이처럼 대상 타입의 주된 기능에 선택적 기능을 '혼합(mixed in)'한다고 해서 믹스인이라 부른다.
[When]
- 자바가 제공하는 다중 구현 메커니즘은 인터페이스와 추상 클래스, 이렇게 두 가지다.
- 자바 8부터 인터페이스도 디폴트 메서드(default method)를 제공할 수 있게 되어 이제는 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다.
[How]
- 래퍼 클래스 관용구(아이템18)와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이 된다.
- 인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 그 구현을 디폴트 메서드로 제공해 프로그래머들의 일감을 덜어줄 수 있다.
- 디폴트 메서드를 제공할 때는 상속하려는 사람을 위한 설명을 @implSpec 자바독 태그를 붙여 문서화해야 한다. (아이템19)
- 디폴트 메서드의 제약
- equals, hashCode를 디폴트 메서드로 제공해서는 안 된다.
- 인스턴스 필드를 가질 수 없고 public이 아닌 정적 멤버도 가질 수 없다(단, private 정적 메서드는 예외다).
- 내가 만든 인터페이스가 아니면 디폴트 메서드를 추가할 수 없다.
- 인터페이스와 추상 골격 구현(skeletal implementation) 클래스를 함께 제공하는 식으로 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 있다. 바로 템플릿 메서드 패턴이다.
- 관례상 인터페이스 이름이 Interface라면 그 골격 구현 클래스의 이름은 AbstractInterface로 짓는다. SkeletonInterface가 더 적절했을지 모르지만, 이미 Abstract를 접두어로 쓰는 형태가 확고히 자리잡았다.
- e.g.) AbstractCollection, AbstractSet, AbstractList, AbstractMap : 핵심 컬렉션 인터페이스의 골격 구현
static List<Integer> intArrayAsList(int[] a) {
Objects.requireNonNull(a);
return new AbstractList<>() {
@Override
public Integer get(int i) {
return a[i]; // 오토박싱(아이템6)
}
@Override
public Integer set(int i, Integer val) {
int oldVal = a[i];
a[i] = val; // 오토언박싱
return oldVal; // 오토박싱
}
@Override public int size() {
return a.length;
}
};
}
// 위 코드는 완벽히 동작하는 List 구현체를 반환하는 정적 팩터리 메서드로, AbstractList 골격 구현으로 활용했다.
// 이 예는 int 배열을 받아 Integer 인스턴스의 리스트 형태를 보여주는 어댑터(Adapter)이기도 하다.
// 익명 클래스(아이템24) 형태를 사용했음에 주목하자.
- 골격 구현 클래스를 우회적으로 이용할 수도 있다.
- 인터페이스를 구현한 클래스에서 해당 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달하는 것이다.
- 아이템18에서 다룬 래퍼 클래스와 비슷한 이 방식을 시뮬레이트한 다중 상속(simulated multiple inheritance)이라 한다.
- 단순 구현(simple implementation)은 골격 구현의 작은 변종으로, 골격 구현과 같이 상속을 위해 인터페이스를 구현한 것이지만 추상 클래스가 아니란 점이 다르다. 이러한 단순 구현은 그대로 써도 되고 필요에 맞게 확장해도 된다.
'독서찰기(讀書札記) > 이펙티브 자바' 카테고리의 다른 글
[아이템 21] 인터페이스는 구현하는 쪽을 생각해 설계하라 (0) | 2022.02.05 |
---|---|
골격 구현(skeletal implementation) 클래스란? (0) | 2022.02.04 |
[아이템 19] 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라. (0) | 2022.01.25 |
[아이템 18] 상속보다는 컴포지션을 사용하라 (0) | 2022.01.25 |
[아이템 17] 변경 가능성을 최소화하라 (0) | 2022.01.09 |