[Effective Java] item20 - 추상 클래스보다는 인터페이스를 우선하라

신민철·2023년 5월 1일
1

Effective Java

목록 보기
20/23
post-thumbnail

추상 클래스와 인터페이스는 모두 인스턴스 메소드를 구현 형태로 제공할 수 있다.

하지만 추상 클래스를 구현하는 클래스는 반드시 하위 클래스가 되어야 한다는 것이다. 자바에서는 단일 상속만 지원하기 때문에 엄청난 부담이 될 것이다.


인터페이스의 장점을 알아보자.

  1. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해넣을 수 있다.
    • Comparable, Iterable, AutoCloseable 인터페이스가 새로 추가됐을 때 표준 라이브러리의 수많은 클래스가 이 인터페이스를 구현한 채로 릴리즈됐다. 이것을 보면 추가하기 굉장히 쉽다는 것이다.
    • 하지만 새로운 클래스를 끼워넣는다는 것은 상당히 제약이 많을 것이다. 먼저 클래스를 상속을 하고 있던 클래스는 상속이 불가능할 것이고 여러 클래스의 공통적인 조상이 되어야 하는데 계층 구조에 굉장히 혼란을 줄 것이다.
  2. 인터페이스는 믹스인(mixin) 정의에 안성맞춤이다.
    • 믹스인은 ‘주된 타입’ 이외에 특정 선택적 행위를 추가 제공하는 것인데, Comparable을 예시로 들자면 해당 인터페이스를 구현한 클래스 간에는 비교가 가능하다. 하지만 추상 클래스는 위에서 말했듯이 기존 클래스에 덧씌울 수 없기에 믹스인은 불가능하다.
  3. 인터페이스는 계층구조가 없는 타입 프레임워크를 만들 수 있다.
    • 예를 들어, Singer, SongWriter라는 인터페이스가 있다고 해보자. 가수 중에는 SongWriter를 하는 사람도 있을 것이다. 인터페이스면 두가지를 모두 구현하면 되지만 클래스로 이것을 구현하려고 하면 매우 비대해질 것이다. 조합의 수는 2의 제곱수로 폭발하게 되는데 이것을 조합 폭발(combinatio-rial explosion)이라고 한다.
  4. 기능을 향상시키는 안전하고 강력한 수단이 된다.
    • 인터페이스의 메소드 중 구현이 명확한 것은 default 메소드로 제공하면 된다. 디폴트 메소드로 제공하면 아이템19에서 나왔던 @implSpec을 이용하여 문서를 제공해야 한다. 하지만 클래스는 상속을 해야하니 활용도가 떨어진다.

인터페이스와 추상 골격 구현(skeletal implementation) 클래스를 함께 제공하여 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 있다. 템플릿 메소드 패턴이다.

static List<Integer> intArrayAsList(int[] a) {
		Objects.requireNonNull(a);

		return new AbstractList<>() {
			@Override
			public Integer get(int i) {
				return a[i]; // Autoboxing (Item 6)
			}

			@Override
			public Integer set(int i, Integer val) {
				int oldVal = a[i];
				a[i] = val; // Auto-unboxing
				return oldVal; // Autoboxing
			}

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

관례상 이름 앞에 Abstract를 붙인다. Skeletal이 더 적절했을지는 모르지만 말이다.

이 예시는 int 배열을 받아 Integer 인스턴스의 리스트 형태로 보여주는 어댑터(Adapter) 이다.

오토박싱, 언박싱이 일어나서 성능상으로는 그렇게 좋지는 않다.


골격 구현 클래스는 추상 클래스처럼 구현을 도와주지만 타입 정의 시의 제약에서 자유롭다. 하지만 추상 클래스는 타입을 그대로 따라야 한다는 제약이 있다.

하지만 이런 식으로 무조건 골격 구현 클래스를 만들어야 하는 것은 아니다. 구조상 골격 구현을 하지 못할 때는 인터페이스를 직접 구현해야 할 것이다. 하지만 이 때는 인터페이스의 디폴트 메소드의 이점을 살릴 수가 있다. 내부에서 private 클래스를 정의하고 메소드 호출을 내부 클래스 인스턴스에 넘겨주는 식으로 말이다.

이것을 시뮬레이트한 다중 상속(simulated multiple inheritance)이라고 한다.

골격 구현 작성은 인터페이스의 기반 메소드를 구현한다. 그리고 만약 인터페이스의 모든 메소드가 기반 메소드라면 골격 구현 클래스를 만들 필요가 없다!

골격 구현은 기본적으로 상속을 가정하므로 아이템19에서 나왔던 것처럼 문서를 남겨야 한다.


단순 구현(simple implementation)은 골격 구현의 작은 변종이다.



핵심 정리
다중 구현을 할 때는 인터페이스의 장점이 많다. 추상 클래스는 인터페이스와 함께 쓰여 템플릿 메소드 패턴으로 쓰인다. 골격 구현은 가능한 한 인터페이스의 디폴트 메소드로 제공하여 구현한 모든 곳에서 사용할 수 있도록 하는 것이 좋다.

0개의 댓글