Interface와 Abstract를 보면 느낌적으로는 차이가 있다는 걸 알지만, 설명을 하고자 하면 말문이 막히곤 한다.
그래서 오늘은 그 느낌적인 차이를 명확히 하고자 둘의 차이를 알아보겠다.
Abstact는 구현을 위임하기 위한 키워드이다.
Abstact 키워드를 사용한 메소드를 추상 메소드라고 하고, 이러한 추상 메소드를 갖는 클래스를 추상 클래스라고 한다.
몇몇 프로그래밍 언어에는 순수 추상 클래스라고 해서 멤버 변수나 그런 것 없이 추상 메소드만 존재하는 클래스를 부르는 말이 있다.
순수 추상 클래스에는 추상 메소드만 있기 때문에, 실질적으로 기능의 구현만을 위임하는 역할을 한다. 기존의 구현부를 어느정도 제공하는 일반적인 추상 클래스와 다르게 순수하게 기능 위임만을 담당하는 것이다.
Java의 Interface는 이러한 순수 추상 클래스의 역할을 조금 더 구체화하고 편리하게 제공하기 위해 독립적으로 나온 기능이라고 볼 수 있다. 실제로 Interface의 모든 메소드들은 public abstract로 선언된다. 내부적으로는 Interface도 abstract를 사용하는 것이다.
즉, Abstract와 Interface는 상속의 기능을 특화하기 위한 개념으로 나온 것이라고 볼 수 있다.
( 실제 둘의 동작은 Java에서 상속의 동작과 똑같다. )
추상 클래스는 상속을 목적으로 존재하고, Interface 기능을 설계하고 구현을 위임하는 것 자체에 목적이 있다.
추상 클래스는 마치 미완성된 Class처럼 동작한다. 부분적으로 완성된 Class의 형태를 제공하면서도 일부 추상 메소드에 대해서 구현을 위임하는 형태이다. 즉, Abstract가 사용된 추상 클래스를 상속받을 경우 Abstract가 붙지 않은 구현 파트가 존재할 수 있게 된다. 반대로 Interface는 모든 기능들의 구현이 강제된다.
( Interface도 Java 8버전 부터는 default 메소드를 통해, 9버전 부터는 private 메소드를 통해 일부 구현부 정의를 허용하고 있다. )
Abstract와 Interface에서 각각 사용되는 키워드가 서로 다른데, 이러한 키워드가 암시하는 의미적 차이는 매우 크다.
Abstract에서 사용되는 extend 키워드는 "확장"이라는 의미를 갖고 있고, 위에서 말했듯 상속을 목적으로 하기 때문에, 클래스들 간에 강한 논리적, 기능적 연관성을 띈다. ( 개인적인 표현일 뿐이다. )
반대로 Interface는 "구현"이라는 implement 키워드를 사용하여, 말 그대로 구현에 초점을 둔다. interface를 구현하는 Class와 논리적 연관성은 크게 없을 수 있고 기능적 연관성이 있다. ( 필자는 기본적으로 Interface를 기능적 설계(계약)라고 생각하지만 사용 의도에 따라 차이가 있을 수 있다고 생각한다. )
둘은 개념적인 차이도 있지만, 실제 사용하기 위한 제약도 조금씩 다르다.
추상 클래스의 경우 크게 다룰 내용이 없다. 상속 개념을 유지하면서 "구현의 위임" 이라는 기능이 추가된 것 뿐이라서 일반적인 클래스의 상속과 사용법이 같고, 상속 받는 클래스가 추상 메소드의 오버라이딩이 강제된다는 점만 있기 때문이다.
다만 Interface의 경우, 기존 상속의 개념에서 구현의 위임이 추가된 것이 아니라, 구현의 위임을 특화하기 위해 독립적으로 분리되어 나온 것이기 때문에 제약 사항이 존재한다.
1. 멤버 변수 선언 불가
Interface 자체가 기능의 설계(계약)으로서 메소드의 구현을 강제하고 위임하기 위한 목적인데, 멤버 변수가 있을 이유가 없다. 그렇기 때문에, Interface에 선언되는 모든 변수에는 public static final이 붙는다.
2. public 접근 제한자만 가능
Interface의 목적은 기능의 셜계이고 공개이다. 기능에 대한 설계를 하고 이는 다른 클래스들에서 일관적으로 호출될 수 있어야 하는데, 접근에 제한을 둔다는 것 부터가 모순이 되기 때문에 public 접근 제한자만이 사용 가능하다.
( 위에서 말했듯 Java 8,9버전 부터는 default, private 메소드를 통해 구현을 할 수 있도록 제공하지만, 특별한 경우를 위한 예외적인 허용일 뿐이다. )
3. 다중 상속, 다중 구현 가능
Interface에는 위처럼 제약이 많은데 반대로 추상 클래스 보다 더 제약이 없는 것도 있다. 바로 다중 상속과 다중 구현이다.
클래스의 상속은 기본적으로 하나의 클래스만 상속 받을 수 있지만, Interface의 경우 Interface끼리 다중 상속을 허용하고, 클래스가 여러개의 Interface를 구현할 수 있도록 다중 구현도 지원한다.
필자는 문득 이런 생각이 들었다.
추상 클래스를 통해 구현을 위임하는 것과 그냥 일반 클래스를 상속받고 interface로 위임하는 것은 어떤 차이가 있을까?
추상 클래스가 "상속 + 구현 위임" 이고 Interface가 "구현 위임"이라면 위의 상황이 차이가 없는 것처럼 느껴진다.
하지만 둘의 기능적 차이가 있기 때문에 적절하게 구분하여 사용하는 것이 좋다.
추상 클래스는 그 자체로 인스턴스화를 허용하지 않기 때문에(익명 클래스 제외) 논리적 연관성이 있는 클래스들의 공통 분모를 설계하여 추상화 하기 위함이라면 추상 클래스가 조금 더 유리할 수 있다.
반대로 일반적인 경우라면 Interface를 사용하는 것이 좋은 경우가 많다.
Interface 기능 자체에만 중점을 두기 때문에 재사용성이 높다. 다른 비슷한 기능적 요구를 하는 클래스들이 생겼을 때 다시 사용할 수 있다.
추상 클래스도 순수 추상 클래스를 만들면 사용은 가능하지만, 클래스의 경우 위에서 말한 것처럼 논리적 연관성이 깊은 경우가 많아서 적절하지 않은 상속이 될 수도 있고, 무엇 보다 다중 상속을 지원하지 않기 때문에, 정작 필요한 상속 클래스가 있는데, 기능적 위임을 위해 상속을 사용한다면 구현이 불가능해진다.
또한 Interface에는 제약이 많아서 안 좋다고 생각할 수 있겠지만, 반대로 제약이 좋은 경우도 있다. Java는 변수를 사용할 때에 Type에 엄격하다. 이것은 엄연히 따지면 제약을 두는 것이고, 범용성이 떨어지지만 반대로 문제 발생 가능성도 현저히 낮아진다. Interface가 추상 클래스에 비해 제약이 많은 만큼 개발자의 실수를 컴파일 시점에 잡을 수 있다.
필자는 Interface가 논리적 연관성이 없을 수 있다고 표현했지만, 이건 꼭 그러라는 법이 정해진 것이 아니다. 개인적으로 Interface가 나오게 된 이유나 개념적으로 봤을 때 그렇다는 느낌이 들었을 뿐이다. 필요하다면 Interface의 다중 상속이나 다중 구현의 장점을 잘 활용해서 추상 클래스의 단점을 돌파하는 방법도 가능하다.
Interface를 사용하는 것이 일반적으로는 좋은 방법이라고 정형화된 듯 하다. 이펙티브 자바라는 책에서도 추상 클래스 보다는 Interface를 사용을 우선시하라는 말이 있다. 그렇다고 해서 Interface를 고집하라는 것은 아니다. 실제로는 Interface와 추상 클래스를 적절하게 혼용해서 둘의 단점을 보완하기도 하고 정말 추상 클래스가 적절한 케이스가 존재하기도 한다. ( 예를 들면 계층 구조가 엄격히 필요한 상황..? )
조금 내용을 어렵게 정리한 것 같은 느낌도 들지만, 개념적으로 제대로 이해해 두는 것이 좋을 것 같아 길게 정리하였다.
한 줄로 요약하자면, 상속에 있어서 메소드 구현의 위임을 위한 Abstract 키워드가 존재하고, 단순히 Abstarct 키워드를 사용한 클래스를 추상클래스, 기능적 설계와 구현에 좀 더 특화하기 위해 만들어진 개념을 Interface라고 표현할 수 있겠다.