JAVA/Effective Java

[아이템 20] 추상 클래스보다는 인터페이스를 우선하라

꾸준함. 2024. 2. 7. 00:21

자바가 제공하는 다중 구현 방식은 아래와 같이 두 가지입니다.

  • 추상 클래스
  • 인터페이스

 
Java 8+ 버전부터 인터페이스도 default method를 제공할 수 있게 되어 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다는 공통점이 있습니다.
한편 이 둘의 가장 큰 차이점은 아래와 같습니다.

 
추상 클래스

 
추상 클래스가 정의한 메서드를 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 같은 타입으로 취급합니다.
자바는 단일 상속만을 지원하기 때문에, 추상 클래스를 이용한 방식은 새로운 타입을 정의하는데 큰 제약이 따릅니다.

 
인터페이스

 
반면 인터페이스에서 정의한 메서드를 모두 정의한 클래스라면 다른 어떤 클래스를 상속했든 같은 타입으로 취급합니다.
추상클래스보다 확장에 용이하기 때문에 item20 제목처럼 추상 클래스보다는 인터페이스를 우선하는 것을 권장합니다.

 

인터페이스의 장점

인터페이스의 장점은 다음과 같습니다:

  • 디폴트 메서드를 제공 가능
  • 기존 클래스도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있음
  • 인터페이스는 mixin 정의에 안성맞춤
  • 계층구조가 없는 타입 프레임워크를 만들 수 있음
  • Wrapper 클래스와 함께 사용하면 인터페이스는 기능을 향상하는 안전하고 강력한 수단

 
1. 디폴트 메서드를 제공 가능

 
Java 8+ 버전부터는 구현이 명백한 메서드에 대해서는 default method로 제공이 가능해 인터페이스를 구현하는 개발자의 일감을 덜어 줄 수 있습니다.
이전에는 인터페이스에 새로운 메서드가 추가되면 해당 인터페이스를 사용하는 모든 구현 클래스가 해당 메서드를 재정의할 때까지 컴파일 에러가 발생했습니다.
그러나 디폴트 메서드를 추가하면 구현체에서 별도로 구현할 필요가 없어 컴파일 에러가 발생하지 않는 장점이 있습니다.
List 인터페이스를 보면 정렬 메서드인 sort()를 디폴트 메서드로 제공하는 것을 확인할 수 있습니다.
 

 
덕분에 List의 구현체인 AbstractList 클래스는 별도로 sort() 메서드를 재정의하지 않아도 되는 것을 확인할 수 있습니다.
 

 
2. 기존 클래스도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있음

 
기존 클래스에 새로운 클래스를 추가할 때 인터페이스가 요구하는 메서드를 추가하고 클래스 선언에 implements 구문만 추가하면 됩니다.
실제로 자바 버전이 올라가면서 추가된 기능을 표준 라이브러리에 추가할 때 인터페이스를 구현하는 방식으로 릴리즈가 되었습니다.

 
3. 인터페이스는 mixin 정의에 안성맞춤

 
mixin개발자가 특정 코드를 다른 클래스에 삽입할 수 있도록 하는 프로그래밍 개념입니다.
간단하게 말해 다른 클래스에서 사용 가능한 메서드를 포함한 클래스를 의미합니다.
대표적인 예시로 아이템 14에서 다루었던 Comparable 인터페이스가 있습니다.
 

 
 
Comparable 인터페이스는 Point 클래스에서 사용할 메서드인 compareTo를 포함하고 있습니다.
만약 Point 클래스가 이미 다른 클래스를 상속하고 있었더라도 Comparable 인터페이스를 구현할 수 있었겠지만, 자바에서는 단일 상속만을 지원하기 때문에 추상 클래스로는 인터페이스와 같이 추가적인 기능을 혼합할 수 없었을 것입니다.
다시 말해, 추상 클래스는 mixin 정의에 부합하지 않습니다.

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

 
타입을 계층적으로 정의하면 수많은 개념을 구조적으로 잘 표현할 수 있지만 현실에는 계층을 엄격히 구분하기 어려운 개념도 존재하며 대표적인 예시로 싱어송라이터가 있습니다.
싱어송라이터는 가수 겸 작곡가인데 가수와 작곡가는 상하 관계가 아니기 때문에 인터페이스로 구현하는 것이 적합합니다.
또한 인터페이스로 구현해야 확장성 및 유연성을 챙길 수 있습니다.
 

 
 
위와 같은 내용을 추상 클래스로 구현할 경우 가능한 조합 전부를 각각의 클래스로 정의한 고도비만 계층 구조가 만들어질 것입니다.

  • 자바에서는 단일 상속만 지원하기 때문에 추상 클래스로 구현 시 각 계층들의 메서드들을 모두 조합한 클래스 정의 필요

 
속성이 n개라면 지원해야 할 조합의 수가 2^n개 즉 조합 폭발(combinatorial explosion) 현상이 발생할 수 있고 이 거대한 클래스 계층 구조에는 공통 기능으로 정의한 타입이 없으므로 자칫 매개변수 타입만 다른 메서드들을 수없이 많이 가진 거대한 클래스를 낳을 수 있습니다.
 

 

 
5. Wrapper 클래스와 함께 사용하면 인터페이스는 기능을 향상하는 안전하고 강력한 수단

 
5.1 인터페이스의 다형성 활용

  • 래퍼 클래스가 특정 인터페이스를 구현하면, 이 래퍼 클래스의 인스턴스는 해당 인터페이스의 다형성을 활용
    • 이는 코드의 유연성을 높이고, 여러 객체들을 일관된 방식으로 다룰 수 있도록 지원

 
5.2 인터페이스의 기능 확장

  • 래퍼 클래스가 특정 인터페이스를 구현하면, 그 인터페이스에 정의된 메서드를 반드시 제공
    • 이로 인해 래퍼 클래스는 특정 기능을 제공하는 것이 보장되며, 인터페이스를 통해 해당 기능에 접근 가능
  • 래퍼 클래스를 통해 인터페이스의 기능 확장 가능

 

추상 골격 클래스

여태까지 인터페이스의 장점만 언급했지만 단점도 존재합니다.

  • default 메서드는 Object 메서드들을 재정의할 수 없음
    • 이유는 링크 참고
    • 굳이 인터페이스에서 Object 메서드들을 재정의 할 필요가 없기 때문에 단점이라고 말하기 애매함

 

  • 인터페이스는 인스턴스 필드를 가질 수 없고 public이 아닌 정적 메서드를 가질 수 없음
  • 본인이 정의한 인터페이스가 아니면 default 메서드를 추가할 수 없음
  • 외부에 노출하고 싶지 않은 메서드들도 모두 public으로 선언됨

 
위 단점들은 추상 클래스에서 일부 해결할 수 있고 추상 골격 클래스를 제공하면 인터페이스와 추상 클래스의 장점을 모두 취할 수 있습니다.

  • 인터페이스를 보며 다른 메서드들의 구현에 사용되는 기반 메서드 선정
  • 기반 메서드들을 사용해 구현할 수 있는 메서드들을 디폴트 메서드로 제공
  • 기반 메서드나 디폴트 메서드로 만들지 못한 메서드는 해당 인터페이스를 구현하는 골격 구현 클래스에서 작성

 
이처럼 추상 골격 클래스는 일부 로직을 완전한 구현체로 제공하고 나머지 로직을 완전한 구현체에 끼어들어갈 수 있는 템플릿으로 제공하기 때문에 디자인 패턴 중 하나인 템플릿 메서드 패턴으로 불립니다.
하위 클래스는 추상 골격 클래스를 상속함으로써 인터페이스의 모든 기능을 처음부터 구현할 필요가 없어지며, 대신에 제공된 기능을 활용하여 더 쉽게 구현할 수 있습니다.

 

주의: 추상 골격 클래스도 상속용 클래스이기 때문에 아이템 19에서 문서화가 반드시 필요합니다.


추상 골격 클래스명은 자바 표준 라이브러리에서 네이밍 컨벤션으로 모두 "Abstract"로 시작하도록 정의되어 있으며, 이에 대표적인 예시로 AbstractList가 있습니다.
 
 

 
 
추상 골격 클래스에 대부분의 메서드들이 구현되어 있기 때문에 개발자는 get, set, size 메서드만 재정의하면 됩니다.
만약 AbstractList가 아닌 List를 구현하려고 했다면 훨씬 많은 메서드를 재정의했어야 했을 것입니다.
 

 
시뮬레이트한 다중 상속 (simulated multiple inheritance)

 
이미 상속을 하여 자바의 단일 상속 제약 때문에 추상 골격 클래스를 직접 상속받지 못할 수 있습니다.
이때 인터페이스를 구현한 클래스에서 해당 골격 구현을 확장한 private 내부 클래스를 정의하고 각 메서드 호출을 내부 클래스의 인스턴스에 전달하는 방식으로 골격 구현 클래스를 우회적으로 이용할 수 있습니다.
 
 

 

 

 

정리

일반적으로, 재사용성, 확장성, 유연성, 그리고 다형성 측면에서 다중 구현용 타입으로는 인터페이스가 가장 적합하며 이에 따라 추상 클래스보다는 인터페이스를 우선하는 것이 권장됩니다.
인터페이스가 복잡해질 경우, 해당 인터페이스를 구현하는 수고를 덜어주는 골격 구현을 함께 제공하는 방법을 고려하는 것도 권장됩니다.

 

비고

클래스와 인터페이스 간 다음의 우선순위가 존재합니다.

  • 클래스가 인터페이스보다 우선순위 높음
  • 보다 구체적인 인터페이스가 우선순위 높음

 

참고

이펙티브 자바
이펙티브 자바 완벽 공략 2부 - 백기선 강사님
https://it-mesung.tistory.com/192

 

[이펙티브 자바] 아이템 20.추상클래스보다는 인터페이스를 우선하라

현재 Java가 제공하는 다중 구현 방식은 두 가지가 있다. 그것은 바로 인터페이스와 추상클래스이다. 그럼 이 둘의 가장 큰 차이점은 무엇일까? 추상 클래스의 경우, 추상 클래스에서 정의한 메서

it-mesung.tistory.com

https://github.com/Meet-Coder-Study/book-effective-java/blob/main/4%EC%9E%A5/20_%EC%B6%94%EC%83%81_%ED%81%B4%EB%9E%98%EC%8A%A4_%EB%B3%B4%EB%8B%A4%EB%8A%94_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC_%EC%9A%B0%EC%84%A0%ED%95%98%EB%9D%BC_%EC%8B%A0%EC%84%A0%EC%98%81.md

반응형