Item20. 추상 클래스보다는 인터페이스를 우선하라

추상 class

추상 class와 인터페이스의 가장 큰 차이점은 추상 class가 정의한 type을 구현하는 class는 반드시 추상 class의 하위 타입이 되야한다는 점이다.

java는 단일 상속만 지원함으로, 추상 class 방식은 새로운 타입을 정의하는데 제약을 갖게 된다. 반면 인터페이스의 경우는 어떤 class를 상속했더라도 같은 type으로 취급된다

기존 class에 새로운 인터페이스를 구현해넣는것은 쉬우나, 새로운 추상 class를 넣기는 힘들다.
두 class가 동일한 추상 class 를 확장하려면 그 추상 class는 계층구조상 두 class의 공통조상이여야 한다.

인터페이스의 장점

1. mixin정의에 알맞다.

mixin : class가 구현할 수 있는 타입으로, mixin을 구현한 class에 원래의 주된 타입 외에 특정 선택적 행위를 제공한다고 선언하는 효과를 준다. 대상 타입의 주된 기능에 선택적 기능을 혼합 (mixin) 한다고 해서 mixin 이라고 부른다.

추상 클래스로는 이중 상속이 불가능하므로, mixin 정의가 불가능하다.

예 : Comparable 은 자신을 구현한 클래스의 인스턴스들끼리 순서를 정할 수 있다고 선언하는 mixin 인터페이스이다.

2. 계층구조가 없는 타입 프레임워크를 만들 수 있다.

현실세계에 있는 계층구조로 표현하기 힘든 개념도 표현이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Singer {

AudioClip sing(Song s);
}
public interface SongWriter {

Song compose(int chartPosition);
}
public interface SingerSongwriter extends Singer,SongWriter{

AudioClip strum();
void actSensitive();
}

인터페이스로는 다중 확장이 가능하기 때문이다. 이를 class구조로 만드려면 가능한 조합 전부를 class로 표현한 무거운 계층구조가 만들어질 것이다.

3. 인터페이스 + 추상 골격 구현 class

인터페이스와 추상 골격 구현(skeletal implementation) class를 함께 제공하여 인터페이스와 추상 class의 장점을 모두 취하는 방법도 있다. 관례상 인터페이스의 이름이 Interface라면, 그 골격 구현 class의 이름은 AbstractInterface로 짓는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package java.util;

/**
* This class provides a skeletal implementation of the {@code Collection}
* interface, to minimize the effort required to implement this interface. <p>
*
* To implement an unmodifiable collection, the programmer needs only to
* extend this class and provide implementations for the {@code iterator} and
* {@code size} methods. (The iterator returned by the {@code iterator}
* method must implement {@code hasNext} and {@code next}.)<p>
*
* ....중략
*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @since 1.2
*/

public abstract class AbstractCollection<E> implements Collection<E> {

java Collection 인터페이스의 추상 골격 클래스인 abstractCollection의 설명은 다음과 같이 적혀있다.

To implement an unmodifiable collection, the programmer needs only to extend this class and provide implementations for the {@code iterator} and {@code size} methods.

이 API를 활용하는 개발자는 abstractCollection을 상속받아서, iterator, size에 관한 로직만 작성해주면 골격 구현 클래스가 나머지 method는 이미 구현을 해놓았기에, 구현을 할필요가 없다. 이를 template method pattern 이라고 한다.

추상 골격 클래스 + 인터페이스 조합을 사용하면 손쉬운 확장이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static List<Integer> intArrayAsList(int[] a){

Objects.requireNonNull(a);

return new AbstractList<Integer>() {
@Override
public Integer get(int index) {
return a[index];
}

@Override
public Integer set(int index, Integer val){
int oldVal = index;
a[index] =val;
return oldVal;
}

@Override
public int size() {
return a.length;
}
};
}

위 예제에서는 익명 클래스를 사용해, 추상 골격 클래스를 구현하였다.

또한 추상 골격 구현 클래스를 우회적으로 이용 가능하다 , 인터페이스를 구현한 class에서 해당 골격 구현을 확장한 private 내부 class를 정의하고, 각 method 호출을 내부 class의 instance에 전달하는 것이다. (simulated multiple inheritance)

추상 골격 class 의 장점을 정리하면 다음과 같다.

  1. 구체 class의 구현을 도와준다.(공통로직은 부모에 작성가능하다)

  2. 인터페이스를 통해 타입을 가질 수 있다.

추상 골격 class 의 작성을 정리하면 다음과 같다.

  1. 다른 method들의 구현에 사용되는 기반 method 를 선택해, 추상 method로 만든다.

  2. 추상 method를 통해 다른 method들을 직접 구현 가능한 method를 모두 default method로 제공한다.

(*equals와 hashCode는 default method로 제공하면 안되며, default method , abstract method로 만들지 못한 method가 있다면 해당 interface를 구현하는 다른 추상 골격 class를 만들어 작성한다. )

정리

다중 구현시 인터페이스를 활용하는 경우가 상속을 여러번 받는경우보다 유리하며, 복잡한 인터페이스의 경우 구현체의 공통로직을 작성해주는 추상 골격 class를 이용하면 구현체 작성이 쉽고, 공통로직을 부모로 분리해줄 수 있다.

Comments