분명 목적에 따라 쓰임이 분명히 다르지만 너무도 헷갈리고 고민되는 두가지를 가져왔다.
각각 어떤 것인지 알아보자.
추상 클래스 (Abstract Class)
추상 클래스(Abstract Class)는 추상 메서드를 선언해 놓고 상속을 통해 자식 클래스에서 메서드를 완성하도록 유도하는 클래스이다. 이러한 특성 탓에 미완성 설계도라고 표현하기도 한다. 추상클래스는 상속을 위한 클래스이기 때문에 따로 인스턴스를 생성할 수 없다.
- 추상 클래스는 일부 메서드가 구현되지 않고 선언만 되어 있는 클래스다. 이러한 메서드를 추상 메서드라고 한다.
- 추상 클래스는 상속을 통해 자식 클래스에서 추상 메서드를 구현하도록 강제한다.
- 추상 클래스는 상태(멤버 변수)를 가질 수 있다.
- 한 클래스는 하나의 추상 클래스만 상속받을 수 있다.
인터페이스 (Interface)
인터페이스도 추상 클래스와 비슷하게 다른 클래스를 작성하는데 도움을 주는 목적으로 작성한다.
다만 인터페이스는 추상 클래스보다 추상화 정도가 높아 추상 클래스와 다르게 구현부가 있는 일반 메서드, 일반 변수 멤버 등을 가질 수 없다. 즉, 인터페이스는 구현된 게 아무것도 없는 기본 설계도라고 할 수 있다. 인터페이스 또한 인스턴스를 생성할 수 없다.
- 인터페이스는 모든 메서드가 구현되지 않고 선언만 되어 있는 순수 추상 타입이다. 인터페이스에 선언된 모든 메서드는 기본적으로 public 이며 추상 메서드이다.
- 인터페이스는 클래스가 인터페이스에 정의된 메서드를 구현하도록 강제한다.
- 인터페이스는 상태(멤버 변수)를 가지지 않는다.
- 한 클래스는 여러 인터페이스를 구현할 수 있다. 이는 다중 상속을 제공한다.
추상 클래스를 사용하는 경우
- 상속 받을 클래스들이 공통으로 가지는 메서드와 필드가 많아 중복 멤버 통합을 할때(인터페이스와의 차이점, 인터페이스는 X)
- 명확한 계층 구조가 필요할때
- 멤버에 public 이외의 접근자(protected, private) 선언이 필요한 경우
- non-static, non-final 필드 선언이 필요한 경우 (각 인스턴스에서 상태 변경을 위한 메서드가 필요한 경우)
- 하위 클래스가 오버라이드하여 재정의하는 기능들을 공유하기 위한 상속 개념을 사용할 때
- 추상 클래스는 상속할 각 객체들의 공통점을 찾아 추상화시켜 놓은 것으로, 상속 관계를 타고 올라갔을 때 같은 부모 클래스를 상속하며 부모 클래스가 가진 기능들을 구현해야할 경우 사용한다.
인터페이스를 사용하는 경우
- 서로 관련성이 없는 클래스들을 묶어주고 싶을때 (형제 관계)
- 다중 상속(구현)을 통한 추상화 설계를 해야할 때
- 특정 데이터 타입의 행동을 명시하고 싶은 경우
- 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용
아래의 표를 통해 간략하게 정리하였다.
추상 클래스 | 인터페이스 | |
정의 | 일부 메서드가 구현되지 않고 선언만 되어 있는 클래스 | 모든 메서드가 구현되지 않고 선언만 되어 있는 순수 추상 타입 |
목적 | 공통된 기본 기능을 구현하고 확장을 위한 기반을 제공 | 다중 상속을 통해 여러 클래스에 공통된 규약을 제공 |
메서드 구현 | 추상 메서드와 일반 메서드 모두 포함 가능 | 주로 추상 메서드만 포함 (자바 9이상에서는 디폴트 메서드 및 정적 메서드 포함 가능) |
상태 보유 | 상태(멤버 변수)를 가질 수 있음 | 상태를 가질 수 없음 (상수 정의 가능) |
생성자 | 생성자를 포함할 수 있음 | 생성자를 포함할 수 없음 |
다중 상속 | 지원하지 않음 (단일 상속만 가능) | 지원함 (여러 인터페이스를 동시에 구현 가능) |
접근 제어자 | 기본적으로 어떤 접근 제어자도 사용 가능 | 모든 메서드는 기본적으로 public |
사용 사례 | 비슷한 종류의 객체들이 공유하는 기본동작을 정의할 때 사용 | 다양한 객체들에게 특정 동작을 강제하기 위해 사용 |
구현 방식 | extends 키워드를 사용하여 상속 | implements 키워드를 사용하여 구현 |
이번에는 그림을 통해 살펴보자.
추상 클래스와 인터페이스는 자기 자신이 new를 통해 객체를 생성할 수 없고, 오로지 자식만이 객체를 생성할 수 있다. 또한 둘 모두 추상 메서드를 가지며 하위 클래스에서 추상 클래스를 모두 구현해야 한다는 공통점이 있다. 하지만 위 그림과 같이 역할이 분명하다.
추상클래스는 여러 자식들이 가지고 있는 공통된 속성들을 뽑아서 하나의 클래스를 만들어 놓았다. 쉽게 생각해서 is a kind of (~의 한 종류)라고 보면 된다. Kevin is a kind of Human 이 그 예가 될 수 있다. 추상 클래스는 이름에서 알 수 있듯이 엄연한 객체다. 비록 단독으로 생성하지는 못하지만 객체이기에 생성자도 사용할 수 있다. 인터페이스와는 완전히 다른 친구다.
반면 인터페이스는 객체가 아니라 추상 자료형이다. 객체가 아니기에 생성자도 사용할 수 없다. 오로지 상수와 추상 메서드만 가질 수 있고 이것을 다른 객체가 구현한다. 이때 be able to (~할 수 있는)이라는 의미를 가진다.
이런 의문을 가질 수 있다. 예를 들어 Swimable 같은 경우에 그냥 Creature 클래스에 추상 메서드를 만들면 되지 않나? 아니면 Human 클래스와 Animal 클래스에 추상 메서드를 각각 만들면 되지않나? 라고 생각할 수 있다. 그렇다면 아래의 코드를 보자.
// 추상 클래스 (조상 클래스)
abstract class Creature {
abstract void swimming(); // 수영 동작을 하는 추상 메서드
}
// 추상 클래스 (부모 클래스)
abstract class Human extends Creature { }
abstract class Animal extends Creature { }
// 자식 클래스
class Kevin extends Human {
void swimming() {
...
}
}
class Turtle extends Animal {
void swimming() {
...
}
}
class Pigeon extends Animal {
void swimming() {} // Pigeon은 수영을 할수 없지만 상속 관계로 인해 강제적으로 메소드를 구현해야하는 사태가 일어난다.
}
이렇게 상속 관계를 설정해 놓고 동작을 하는 메서드를 추가해야 하는데, 만일 수영 동작을 하는 swimming() 메소드를 각 자식 클래스에 추가해야 한다고 하자.
이때 나중에 확장을 위해 추상화 원칙을 따른다. 그러면 부모나 조상 클래스에 추상 메서드를 추가해야 하는데, Kevin과 Turtle을 동시에 포함하는 Creature 추상 클래스에서 추상 메서드를 추가한다.
하지만 Creature 추상 클래스에 추상 메서드를 추가하면, 곧 이를 상속하는 모든 자식 클래스에서 반드시 메서드를 구체화 해야한다는 규칙 때문에 실제로 수영을 못하는 Pigeon 클래스에서도 메서드를 구현해야 하는 강제성이 생기게 된다.
물론 메서드를 선언하기만 하고 빈칸으로 놔두면 되기는 하지만, 이는 객체 지향 설계에 위반될 뿐만 아니라 나중에 유지보수 면에서도 마이너스 적인 효과가 된다.
따라서 상속에 얽매히지 않는 인터페이스에 추상 메소드를 선언하고 이를 구현(implements) 하면서 자유로운 타입 묶음을 통해 추상화를 이루게 하는 것이다.
또한 생명체에서만 상속되도록 하면 생명체가 아닌데도 수영할 수 있는 것에 대해 구현을 하기가 복잡해진다. 따라서 인터페이스로 구현한다면 부모가 공통적이지 않은 자식 클래스까지 선택적으로 유연하게 적용을 할 수 있다.
마치며
정리하자면 추상 클래스는 is kind of 관계로 관련성이 높은 명확한 계층 구조의 추상화에 사용되며, 인터페이스는 is able to 관계로 관련성이 낮더라도 기능에 따른 추상화가 필요하거나 다중 상속(구현)이 필요할 때 사용된다.
즉, 무엇을 사용해야할지 고민되는 순간이 온다면 추상 클래스는 클래스 간의 연관 관계를 구축하는 것에 초점을 둔다고 생각하고, 인터페이스는 클래스와 별도로 구현 객체가 같은 동작을 한다는 것을 보장하기 위해 사용하는 것에 초점을 맞추는 것이라고 생각하자.
참고 출처 :
1. https://coding-factory.tistory.com/868
'JAVA' 카테고리의 다른 글
업캐스팅(Upcasting) (feat. 다운캐스팅) (1) | 2024.03.05 |
---|---|
오버로딩(Overloading) vs 오버라이딩(Overriding) (2) | 2024.03.05 |
DTO와 VO는 같은 건가? (1) | 2024.02.28 |
생성자를 메서드라고 할 수 있을까? (1) | 2024.02.24 |
메서드의 오버로딩(Overloading) (feat. 정적 바인딩) (0) | 2024.02.23 |