본문 바로가기
Language/자바&코틀린

인터페이스와 추상클래스의 차이

by 코딩공장공장장 2024. 7. 2.

인터페이스 동일한 행위를 클래스들이 구현하도록 지정한 추상 자료형으로 일종의 계약서 또는 설계서이다.

추상클래스 하나 이상의 추상 메서드를 포함한 클래스를 추상 클래스라고 하며 복제와 확장의 목적으로 쓰인다.

(추상 메서드 없이 추상 클래스 선언 가능하나 구체 클래스와 차이가 없음)

 

인터페이스와 추상클래스에 대한 정의를 간단하게 한줄로 표현해봤다.

인터페이스와 추상클래스는 모두 인스턴스화가 불가하고 구현체에서 추상 메서드를 반드시 오버라이딩 해야한다는 공통점이 있다.

허나, 인터페이스와 추상 클래스는 구조적으로 큰 차이가 있고, 이에 의하여 그 사용성에도 큰 차이점을 드러낸다.

 

인터페이스와 추상클래스의 차이점

  1. 구현부의 존재여부
  2. 다중상속 여부
  3. is-a와 can-do 관계

이외에도 많은 차이가 있지만 이 두가지에 대해 깊이있게 다루도록 하겠다.

다른 차이점은 글의 최하단에 표를 통해 정리해 놓았다.

 

1. 구현부의 존재여부

인터페이스는 메서드의 구현부가 없는 추상 메서드로만 이루어져있다. 이에 반면 추상 클래스는 메서드의 구현부가 존재한다.

이는 추상 클래스와 인터페이스의 가장 큰 차이로 이 차이점으로 인해 다양한 차이점이 파생된다.

 

결합도

추상 클래의 구현부는 하위 클래스에 그대로 복제된다.

추상 클래스는 extends 라는 키워드로 상속을 진행하는데, 이 extends라는 영어 단어 처럼 확장되는 구조이다.

코드가 그대로 복제 확장되기에 캡슐화가 깨지며, 구현부에 의존하는 결합도가 높은 구조를 갖게 된다.

추상 클래스에는 메서드의 구현부만 존재하는게 아니라 속성(프로퍼티)까지 존재하기 굉장히 높은 결합도를 유발한다.

구현부와 속성의 변경사항은 모든 하위 클래스에 영향을 끼치게 되는 것이다.

 

인터페이스상 메서드에만 의존하고 구현부가 존재하지 않으므로 캡슐화가 깨지지 않고, 결합도가 낮은 구조를 가질 수 있다.

추상 클래스와 비교해 상대적으로 상위 타입에 의존하는 자원이 적다.

 

사용목적

추상 클래스가 결합도가 높다고 해서 나쁜것만은 아니다. 코드의 복제가 필요하다면 추상 클래스를 사용하는 것이 좋다.

코드의 복제를 통한 코드 중복제거는 추상 클래스의 가장 큰 장점이자 주 사용 목적이다.

 

인터페이스의 경우 변경사항에 유연한 구조를 갖추고 있기 때문에 기능 변경, 확장이 많이 일어나는 구조라면 인터페이스를 사용하는 것이 바람직하다.

소프트웨어의 세계에서 변경사항은 뗄래야 뗄수 없는 관계이므로 코드 복제를 제외한 거의 대부분의 상황에 인터페이스를 쓰는게 바람직하다고 볼 수 있으며 그 사용성도 추상클래스보다 높다.

 

참고로 추상 클래스가 자주 사용되는 디자인 패턴은 템플릿 패턴이고, 인터페이스가 자주 사용되는 디자인 패턴은 전략패턴인데,
이를 이해한다면 추상클래스와 인터페이스를 구분하는데 도움이 될 것이다.

 

템플릿 메서드 패턴, 전략 패턴

 

2. 다중상속 여부

인터페이스의 경우 다중 상속이 가능하나, 추상 클래스는 다중 상속이 불가능하다.

(구체 클래스 또한 다중 상속 불가)

이또한 사실 구현부의 존재여부 차이에 의해 파생된 또다른 차이점이다.

 

 

위와 같이 Person이라는 추상 클래스가 존재하고 이를 상속 받은 Father, Mother이라는 구현 클래스가 존재한다고 하자.

Father, Mother은 추상 메서드인 move를 오버라이딩 하였다.

이때 Child가 Father, Mother를 상속 받으려고 한다면 둘 중 누구의 move 메서드가 복제될지 결정할 수 없다.

 

자바는 이러한 구조적 문제로 클래스의 다중 상속을 지원하지 않는다.

 

허나, 인터페이스의 경우 이러한 문제가 생겨나지 않는다.

위와 같이 Father와 Mother가 모두 인터페이스라고 한다면 구현부가 존재하지 않기에

어떤 추상 메서드를 상속 받아도 문제 될일이 없고 Child에서 반드시 오버라이딩을 진행하므로 구조적 문제가 발생하지 않는다.

 

허나, 자바 8 이후 인터페이스에 등장한 default 메서드의 경우 다이아몬드 문제가 발생할 수 있다.

 

 

  • [클래스와 인터페이스 상속] -> 클래스 우선
  • [계층 구조의 인터페이스 상속] -> 상속 받는 default 메서드 우선
  • [인터페이스 다중 상속] -> 오버라이딩을 통한 재정의 그렇지 않으면 컴파일 에러

 

아래와 같이 명시적으로 누구의 default 메서드를 사용할지 지정할 수 있고 또는 아에 새롭게 오버라이딩할 수 있다.

public class Child implements Father, Uncle {

   @Override

   public void move() {

       Father.super.move();

   }

}

 

참고로 자바8 부터 인터페이스에 static 메서드가 추가됬는데, static 메서드는 다이아몬두 문제에 고려하지 않아도 된다.

static은 애초에 오버라이딩이 없다. 인스턴스화 해서 사용하는게 아니고 Class.move() 이렇게 사용하니 문제 될게 없다.

 

3. is-a 관계와 can-do 관계

추상 클래스의 상속관계는 is-a, 인터페이스의 상속관계를 can-do의 특성을 갖는다.

 

추상 클래스는 속성과 행위를 하위 구현체에 그대로 물려주는 계층 구조를 형성한다.

인터페이스는 추상메서드만 존재하기에 ‘can-do’라는 행위를 할 수 있는지 여부로만 구현을 결정할 수 있다.

 

인터페이스는 행위의 가능 여부만 따지기에 작게 분리시키는것이 가능하지만,

추상 클래스는 속성과 행위가 하나로 묶여있기에 작게 분리하는 것이 어렵다. 

다중상속 또한 불가능하기에 계층 구조를 형성하는 상위타입의 추상 클래스는 필요한 모든 요소를 갖추고 있어야한다.

 

따라서 추상 클래스를 설계할 때에는 ‘하위 클래스는 상위 클래스이다’라는 엄격한 is-a 관계를 고려해야한다.

추상클래스에서 정의한 속성과 행위, 그리고 하위 구현체에서 오버라이딩 하는 행위들이 모두 일관성있게 정의되야 한다.

 

인터페이스에 비해 추상 클래스의 is-a관계가 엄격하게 강조되는 이유는

추상 클래스에 정의된 모든 속성과 행위가 계층 구조를 형성하는데 일관성이 있어야 하기 때문이다.

 

인터페이스와 추상클래스 비교

  추상 클래스 인터페이스
선언 abstract interface
메서드 제한 없음 추상 메서드
static 메서드(자바8)
default메서드(자바8)
private 메서드(자바9)
변수 제한 없음 상수(static final)만 가능
접근제어자 제한없음 public만 가능(생략가능)
다중상속 X O
공통점
  • 인스턴스화 불가
  • 구현체가 반드시 추상 메서드를 재정의해야함

 

[번외] 자바 인터페이스에default, static, private 메서드가 추가된 이유

default 메서드

첫번째 주요한 이유는 하위 호환이다.

기존에 제공되고 있는 인터페이스의 결함이나 비효율으로 변경이 필요한 경우 기존 메서드를 default 메서드로 변경하여 이전에 작성된 구현체들과 호환을 이루도록 하고, 새로운 추상 메서드를 제공하여 이후의 구현을 강제화 할 수 있다.

 

대표적인 예로 스프링의 ApplicationEventPublisher 있다.

ApplicationEventPublisher의 publishEvent(ApplicationEvent event)메서드는 ApplicationEvent 하위 타입의 객체 대해서만 이벤트를 발행하도록 하였다.

허나, 스프링에서 ApplicationEvent 상속 없이도 모든 객체에 대하여 이벤트를 발행할 수 있는 구조를 제공하고자
기존의 publishEvent(ApplicationEvent event)를 default 메서드로 바꿔 이전에 작성된 구현체들과 호환을 이루고 

새롭게 정의한 publish(Object event)를 통해  ApplicationEvent 상속 없이도 사용 가능하게 하였다.

두번째 이유는 선택적 구현 목적이다.

예시를 통해 설명하겠다. 스프링의 HandlerInterceptor는 컨트롤러 아래와 같은 세개의 메서드가 존재한다.

  • preHandle : 컨트롤러 이전 실행
  • postHandle : 컨트롤러 정상 처리 이후 실행
  • afterCompletion : 예외가 터지더라도 컨트롤러 이후 반드시 실행

HandlerInterceptor는 컨트롤러 이전 이후에 실행할 수 있는 로직을 구현할 수 있게 해주는 인터페이스이다.

세개의 메서드 모두 default 메서드로 구현되어있기에 개발자는 로직을 추가할 필요한 시점에 실행될 메서드를 선택적으로 구현할 수 있다.

 

static 메서드

static메서드는 클래스에 고정된 메서드이다. 오버라이딩 가능하지도 않고, 인스턴스를 통해 접근하는 메서드 또한 아니다.

오버라이딩을 통한 하위 구현체에서 다형성을 표현할 수 없는 static 메서드가 굳이 인터페이스에 없을 이유가 없었다.

자바의 지나친 인터페이스 추상화가 static 메서드의 부재를 만들었고 8 이후에 static 메서드가 추가 되었다.

 

private 메서드

private 메서드는 default 메서드와 static 메서드의 관심사를 분리하기 위한 목적으로
default 메서드와 static 메서드에 모든 로직을 구현하면 메서드가 비대해질 수 있으므로 private 메서드를 통해 

구현을 분리하기 위하여 자바9 부터 추가 되었다.

반응형