자바에서는 다중 구현 메커니즘은 인터페이스와 추상 클래스, 이렇게 2가지이다. 이 둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다는 점이다. 자바의 경우 단일 상속만 지원하니, 추상 클래스 방식은 새로운 타입을 정의하는데 큰 제약을 안게 된다. 인터페이스의 경우 선언한 메서드 규약만 잘 지킨다면, 어떤 클래스를 상속했던 간에 같은 타입으로 간주한다.
기존 클래스에 타입을 추가하기 쉽다. 그 이유는 인터페이스는 선언된 메서드를 구현해주기만 하면 되기 때문이다. 클래스에 의존하지 않으며 단순히 메서드 추가만 이루어지기 때문에 가볍다. 반면에 추상 클래스는 상속을 기반으로 하기 때문에 추상 클래스에 의존하게 되고, 상속
이기 때문에 고려해야 할 점이 많다.(하위 클래스 상속 테스트, 생성자에 오버라이딩 메서드 금지, 문서 작성, 그 외 상속시 발생하는 사이드 이펙트 등등)
자바에서는 단일 상속만 허용하기 때문에 상속을 바탕으로 구현하는 추상 클래스의 해당 클래스에 여러 타입을 정의할 수 없다. 반면에 인터페이스는 여러 타입을 정의(mixin)할 수 있기 때문에 기존 클래스에 Comparable이든, AutoCloseable이든 무엇이든 타입을 추가만 해주면 해당 타입이 된다.
타입을 계층적으로 정의하면 개념을 구조적으로 잘 나타낼 수 있다. 그러나 예로 가수와 작곡가를 생각해보자. 가수는 노래를 부른다. 작곡가는 노래를 작곡한다. 우리는 2개의 개념으로 타입을 분리했지만 2직업을 동시에 가진 사람도 존재한다. 이런 경우 계층 구조가 애매해지는데 인터페이스는 이를 쉽게 믹스인 할 수 있다.
public interface Singer {
}
public interface SongWriter {
}
public interface SingerSongWriter extends Singer, SongWriter{
}
이런 경우가 흔하진 않지만 리팩토링을 하거나 기존 프로젝트에 무엇인가 추가해야할 상황이 생길 때, 꽤나 결정적인 도움을 줄 수 있다.
인터페이스는 클래스를 여러 타입으로 정의가능한데 이런 타입의 유연성이 컴포지션에 효율적이다. 전략 패턴, 상태 패턴 등등 디자인 패턴에서 타입의 다형성을 활용해 클래스 구조를 효율적으로 작성한다. 이 때 인터페이스는 강력한 힘을 발휘한다.
추상 클래스가 나쁘기만 한 것은 아니다. 구현이 매우 명확하고, 명확한 골격 구조가 필요하다면 템플릿 패턴을 사용할 수 있다. 템플릿 패턴이란, 상속을 통해 코드의 중복을 줄이고, 구현 목록의 골격을 미리 정의하는 것이다.
기존 자바 라이브러리 중 Collection만 보더라도 Abstract가 접두사로 붙은 추상 클래스가 굉장히 많다. 이렇게 추상 클래스를 사용하면 equals and hashcode 또는 공통된 코드 구현을 굉장히 효과적으로 줄일 수 있다. 인터페이스의 경우 API만 정의할 뿐 구현을 하지는 않기 때문이다. default로 가능해지긴 했지만 명확하다면 default보단 추상 클래스가 좀 더 낫다. 다만 상속이기 때문에 항상 사이드 이펙트를 염두해두고 개발해야 한다.