본문 바로가기
JAVA/Effective Java

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

by Garonguri 2022. 5. 3.
728x90

인터페이스와 추상 클래스

 

공통점

- 인스턴스로 생성이 불가능하다.

- (자바 8부터) 인스턴스 메서드를 구현 형태로 제공할 수 있다.

- 선언부만 있는 추상 메서드를 갖는다.

 

차이점

- 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다.

(새로운 타입을 정의하는 데 제약 있음)

- 인터페이스가 선언한 메서드를 모두 정의하고 일반 규약을 잘 지켰다면 다른 어떠한 클래스를 상속했든 같은 타입으로 취급된다.

 

- 추상 클래스 :

  • 단일 상속만 지원하기 때문에, 다중 클래스 상속이 불가능
// idol에 Entertainer을 추가로 상속 받고 싶어도 할 수 없음.
public class Idol extends Singer {
  // ...
}
abstract class Entertainer {
  // ...
}

- 인터페이스 :

  • 구현해야하는 메소드만 구현하면 어떤 클래스를 상속하던 간에 같은 타입으로 취급한다.
  • 여러 인터페이스 상속 가능하다.
// Idol에 Entertainer을 추가로 상속 받고 싶으면 implements 구문만 추가하면 된다.
public class Idol extends Singer implements Entertainer {
  // ...   
}
interface Entertainer {
  // ...
}

[ 기존 클래스에 인터페이스를 구현하는 방법 ] 

- 쉽다.

인터페이스가 요구하는 메서드를 추가하고 클래스 선언부에 implements 구문을 추가한다.

 

[ 기존클래스에 추상 클래스를 끼워넣는 방법 ]

- 어렵다.

두 클래스가 같은 추상 클래스를 확장하길 원한다면 해당 추상 클래스는 두 클래스의 공통 조상이어야 한다.

* 클래스 계층 구조에 혼란을 일으킬 수 있다.

 


[ 인터페이스의 장점 ]

 

1. mixin정의에 안성 맞춤이다.

- 믹스인이란? 클래스가 구현할 수 있는 타입으로, 믹스인을 구현한 클래스는 원래 타입 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다.

- 쉽게 말해 대상 타입의 주된 기능에 선택적 기능을 혼합하는 것.

- 추상 클래스의 경우 단일 상속만 지원하기 때문에 믹스인을 구현할 수 없다.

 

2. 인터페이스로는 계층 구조가 없는 타입 프레임워크를 만들 수 있다.

- 현실에서는 계층을 구별하기 어려운 개념이 존재하기 때문에 장점

- 타입을 인터페이스로 정의하면 클래스가 인터페이스 모두를 구현할 수 있다.

- 또한 인터페이스 여러개를 확장하고 새로운 메서드까지 추가하는 제 3의 인터페이스까지 만들 수도 있다.

 

3. 조합 폭발 시 도움이 된다.

- 조합 폭발이란? 거대한 클래스 계층 구조에서 자칫 매개변수 타입만 다른 메서드들을 수없이 많이가진 거대한 클래스가 생기는 경우

 

4. 래퍼 클래스와 함께 사용하면 기능을 향상시키는 안전하고 강력한 수단이 된다.

- 타입을 추상 클래스로 정의한다면 상속으로만 기능을 추가할 수 있는데 그렇게 된다면 래퍼 클래스보다 활용도가 떨어지고 깨지기는 쉽다.

 


인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 디폴트 메서드로 제공하자.

- 디폴트 메서드?

: 구현 내용이 있는 메소드로, 구현 방법이 명확하다면 인터페이스에서 사용 가능하다.

- 디폴트 메서드로 제공할 때는 @implSpec 자바독 태그를 붙여 문서화하자.

 

[ 디폴트 메서드 규약 ]

1. Object method(equals나 hashcode 등)은 디폴트 메서드로 제공하지 말자.

2. 인터페이스는 인스턴스 필드를 가질 수 없고 (private을 제외한) public이 아닌 정적 멤버도 가질 수 없다.

3. 내가 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없다.

 

- Java 9 이후부터는 private static 메서드도 구현이 가능하게 변경됨.


5. 인터페이스와 추상 골격 구현 클래스를 함께 제공한다면 인터페이스와 추상 클래스의 장점을 모두 취할 수 있다.

- 인터페이스로는 타입을 정의하거나 디폴트 메서드를 제공하고, 골격 구현 클래스는 나머지 메서드들을 구현한다.

-> 템플릿 메서드 패턴! (인터페이스 + 골격 구현 클래스)

 

- 관례상 인터페이스 이름 앞에 Abstract을 붙인 것이 해당 인터페이스의 골격 구현 클래스이다.

- 골격 구현 클래스는 추상 클래스처럼 구현을 도와주는 동시에 추상 클래스로 타입을 정의할 때 따라오는 제약에는 자유롭다.

- 골격 구현을 확장하지 못하는 상태라면 인터페이스를 직접 구현하자.

- 인터페이스를 구현한 클래스에서 해당 골격 구현을 확장한 private 내부를 정의하고, 각 메서드 호출을 내부 클래스 인스턴스에 전달하는 식으로 골격 구현 클래스를 우회적으로 이용할 수도 있다. (시뮬레이트한 다중 상속) -> 다중 상속의 장점은 가져가고 단점은 피하게 해준다.

 

[ 골격 구현 작성법 ]

1. 인터페이스를 확인해 다른 메서드들의 구현에 사용되는 기반 메서드를 제공한다. 위의 기반 메서드는 골격 구현에서 추상 메서드가 된다.

2. 기반 메서드들을 사용해 직접 구현할 수 있는 메서드들을 모두 디폴트 메서드로 제공한다. (object method는 안되는거 알지요?)

* 인터페이스의 메서드가 모두 디폴트 메서드라면 골격 구현 클래스를 별도로 만들 필요는 없음.

3. 남아있는 메서드가 있다면 이 인터페이스를 구현하는 골격 구현 클래스를 하나 만들어 남은 메서드를 작성한다.

* 골격 구현 클래스에는 필요시 public 이 아닌 필드나 메서드를 추가해도 괜찮다.

 

- 골격 구현은 기본적으로 상속해 사용하는 것을 가정한다.

- 골격 구현은 반드시 동작 방식을 잘 정의하여 문서로 남겨야 한다.

 


- 단순 구현은 골격 구현의 작은 변종으로, 상속을 위해 인터페이스를 구현한 것이지만 추상 클래스가 아니다.

 

핵심 정리

- 일반적으로 다중 구현용 타입으로는 인터페이스가 적합하다.
- 복잡한 인터페이스라면 골격 구현을 함께 제공하자.
- 골격 구현은 가능한 한 인터페이스의 디폴트 메서드로 제공하여 그 인터페이스를 구현한 모든 곳에서 활용하도록 하자.

 

728x90

댓글