성장기록지
코틀린 추상 클래스와 인터페이스의 차이 본문
추상 클래스와 인터페이스의 차이를 설명하세요
코틀린이랑 그렇게 친하지 않은 사람은 별 차이가 없다고 생각을 할 수 있다.
명확한 차이를 확인하도록 기능적 차이와 개념적 차이를 하나씩 파헤쳐 보도록 하겠다
독자는 추상 클래스와 인터페이스가 뭔지는 안다는 가정하에 작성해보겠다.
기능적 차이
1. 인터페이스는 생성자를 만들 수 없다.
인터페이스는 추상클래스와 다르게 생성자를 만들 수 없다.
왜 이렇게 구성하였는지의 의도는 개념적 차이에서 보충하겠다.
2. 인터페이스는 프로퍼티의 상태 저장이 불가능하다.
간단한 예시와 함께 보여드리겠다.
추상클래스는 다음과 같이 값을 지정해 둘 수 있다.
하지만 인터페이스는 다음과 같이 상태 저장이 불가능하다.
왜 인터페이스는 상태 저장이 안되게 만들었을까? 이것도 중요한 내용이니
이후에 다룰 개념적 차이에서 자세히 다루겠다.
3. 다중상속, 단일 상속
인터페이스는 다중 상속이 가능하다.
예시를 통해 살펴보도록 하자.
Animal과 Flyable라는 인터페이스 두개를 상속받아, Bird라는 class를 구현한 예시이다.
반면에 추상 클래스는 다중 상속이 불가능하다.
4. 인터페이스는 일반 함수도 상속 가능
추상 클래스는 abstract가 붙은 함수는 본문을 가질 수 없게 된다.
또한 추상클래스 내에서 fun을 붙여서 만든 함수는 open 키워드를 붙이지 않으면
오버라이딩 할 수 없다.
반면에 인터페이스는 fun으로 구현된 함수도 오버라이딩이 가능하다!
이렇게 기능적 차이점을 하나씩 살펴보아도 아직 크게 차이점과 그들의 의도가 와닿지 않는다.
당연한 얘기인게, 서로는 비슷하지만, 사용되는 개념적 용도를 다르게 만든 것 이기 때문이다!
그렇다면 개념적 차이를 살펴보며 서로 어떻게 다르게 활용하면 좋을지 알아보자.
개념적 차이
인터페이스
인터페이스라는 말을 코딩 말고 들어본 경험이 있을것이다.
게임을 좋아한다면 한번쯤 "유저 인터페이스" 라는 말을 들어봤을텐데,
이걸 영어로 부르면 User Interface로 , 흔히들 말하는 UI이다.
그렇다면 여기서 Interface라는 말은 어떤 의미로 쓰일까?
"인터페이스"는 사용자가 시스템과 상호작용하는 방식을 제공하는 매개체 또는 접점이라는 의미로 사용된다.
풀어서 말하면, 게임을 하는 유저가 게임의 시스템과 상호작용하기 위한것이 인터페이스라는 것이다.
그렇다면 다시 코틀린 세계로 넘어와서 Interface를 생각해보자.
인터페이스를 만드는 사람은 인터페이스를 받아 구현을 할 사람에게 상호작용 하기 위한 접점을 제공해야한다.
인터페이스를 통해서 마치 명세서와 같이, 이렇게 구현했으면 좋겠다! 라는 의도를 전달한다는 뜻이다.
그렇다면 이제 인터페이스에서 왜 프로퍼티의 상태를 저장하지 못하게 하는지 추측해볼 수 있다. 바로 구현의 세부사항이 아닌, 동작의 정의에 초점을 두었기 때문이다.
세세한 값들은 구현하는 사람이 알아서 정하고(구현하는 class에서 정하고), 어떻게 동작하게 할지만 만들자는 인터페이스의 취지 때문인 것이다.
인스턴스를 생성할 책임이나 상태를 관리하는 역할을 맡지 않기 때문에 마찬가지로 생성자도 구성할 수 없게 설계하였다.
즉, 인터페이스는 구현자에게 동작의 규칙과 구조만 제공하며, 구체적인 상태나 데이터는 구현자에게 맡긴다.
추상 클래스
앞서 인터페이스는 구현자에게 "이렇게 동작을 만들어주세요!"라는 규칙과 접점을 제공하는 역할이라고 했다.
반면, 추상 클래스는 규칙을 제공하기보단, 여러 객체의 공통점들을 묶어서 상속받을 수 있게 하는 의도이다.
아래와 같이 countAge()이라는 함수를 People의 공통점으로 묶어서 상속을 받게 하는 의도로 작성할 수 있다.
age라는 프로퍼티의 값도 저장할 수 있다!
그렇다면 인터페이스도 countAge()을 공통점으로 묶어서 상속받게 할 수 있으니 같은거 아니냐? 라고 할 수 있다.
위의 기능적 차이에서 설명했듯이. 인터페이스는 fun으로 상속해도 오버라이딩이 가능하다.
왜냐면 인터페이스는 기본적인 구조를 제공하는 것이므로,
이를 받아서 구체적이게 구현할 수 있게 설계했기 때문이다!
abstract class People(val age: Int) {
abstract fun sound()
fun countAge() {
println("벌써 $age 살")
}
}
class Seolseongbeom(age: Int) : People(age) {
override fun sound() {
println("NO")
}
}
class LeeHun(age: Int) : People(age) {
override fun sound() {
println("YES")
}
}
정리
둘의 특징을 요약하자면 다음과 같다.
인터페이스
- 클래스 간 공통 동작을 규칙으로 정의하고 싶을 때 사용한다.
- 상태를 저장하지 않고, "이런 방식으로 구현하세요"라는 약속을 전달하고 싶을 때 사용한다.
추상 클래스
- 클래스 간 공통 상태와 동작을 묶어 상속받는 클래스들이 공유할 때 사용한다.
- 상태(프로퍼티)를 포함하고, 일부 공통 동작은 미리 구현하며,
나머지는 상속받는 클래스에서 세부적으로 정의하고 싶을 때 사용한다 - 단일 상속만 허용되는 구조를 설계하고 싶을 때도 사용한다.
마치며
이제 코드를 짤 때 상황에 따라 무엇이 적절한지 어느정도 감이 잡혔을 것이다.
작성자도 이 글을쓰며 안드로이드에서 커스텀 리스너를 왜 인터페이스로 작성하는지 의도를 알 수 있었다.
마지막으로 예상 질문 및 생각해 볼만한 질문을 남기고 글을 마치도록 하겠다.
생각해 볼만한 질문
- 추상 클래스를 사용하는 경우와 인터페이스를 사용하는 경우를 비교해주세요.
- 실제 구현에서 추상클래스와 인터페이스를 어떻게 활용하였는지 말씀해주세요.
- kotlin에서 추상 클래스와 인터페이스를 비교할 때, JVM 바이트코드에서 두 구조가 생성되는 방식의 차이를 설명하세요.
- 인터페이스는 fun을 그냥 Overriding 할 수 있다고 하셨는데, 추상클래스에서 open으로 fun을 Overriding하는 것과 무슨 차이가 있나요?
'코틀린' 카테고리의 다른 글
[Kotlin] 구조 분해와 Component함수, 분해 가능 개수 (0) | 2025.01.06 |
---|---|
코틀린 sealed class와 실제 활용 예시 (0) | 2025.01.04 |
코틀린 Data Class 자세히 알아보기 (0) | 2024.12.24 |