보통의 상위 클래스는 자기 자신에 대한 인스턴스를 만들 수 있는 타입들이다. 하지만 별도의 인스턴스가 있을 수 없고 구체적인 경우를 구현한 다른 클래스의 인스턴스만 만들 수 있는 추상적인 개념을 표현하는 클래스라면 인스턴스 생성은 바람직하지 않다.
그렇다면 추상적인 개념을 표현하려면 어떤 타입의 클래스를 사용해야 할까? 이 질문에 대한 대답이 바로 추상 클래스이다.
추상 클래스란?
추상 클래스는 자바에서도 지원하는 방식이다. 추상 클래스는 직접 인스턴스화 할 수 없고 다른 클래스의 상위 역할만 할 수 있는 클래스를 뜻한다. 클래스를 추상 클래스로 만들기 위해서는 abstract라는 변경자 키워드를 붙여야 한다.
abstract class Entity(val name: String)
// Success: 하위 클래스에서 위임 호출
class Person(name: String, val age: Int): Entity(name)
// Error: 추상 클래스의 인스턴스를 생성할 수 없음
val entity = Entity("Unknown")
위의 예제를 보면 추상 클래스에도 생성자가 있을 수 있다. 추상 클래스와 비추상 클래스의 차이는 추상 클래스의 생성자가 오직 하위 클래스의 생성자에서 위임 호출로만 호출될 수 있다는 점이다.
추상 클래스의 또다른 특징은 추상 멤버를 정의할 수 있다는 것이다. 아래의 코드를 봐보자.
import kotlin.math.PI
abstract class Shape {
abstract val width: Double
abstract val height: Double
abstract fun area(): Double
}
class Circle(val radius: Double): Shape() {
val diameter get() = 2 * radius
override val width: Double get() = diameter
override val height: Double get() = diameter
override fun area(): Double = PI * radius * radius
}
class Rectangle(
override val width: Double,
override val height: Double
) : Shape() {
override fun area(): Double = width * height
}
fun Shape.print() {
println("Bounds: $width * $height, area: ${area()}")
}
fun main() {
Circle(10.0).print()
Rectangle(3.0, 5.0).print()
}
추상 멤버 자체는 구현을 가질 수 없으므로 추상 멤버를 정의할 때는 몇 가지 제약이 있다.
- 추상 프로퍼티를 초기화할 수 없고 명시적인 접근자나 by 절을 추가할 수 없다.
- 추상 함수에는 본문이 없어야 한다.
- 추상 프로퍼티와 함수 모두 명시적으로 반환 타입을 적어야 한다. 본문이나 초기화 코드가 없으므로 타입을 추론할 수 없기 때문이다.
인터페이스란?
근본적으로 인터페이스는 추상 혹은 비추상 메서드나 프로퍼티를 포함하지만 자체적인 인스턴스 상태나 생성자를 만들 수 없는 타입이다.
인터페이스 멤버는 디폴트가 추상 멤버다. 인터페이스는 클래스나 다른 인터페이스의 상위 타입이 될 수 있다. 비추상 클래스가 인터페이스를 상속할 때는 모든 추상 멤버에 대한 구현을 제공해야 한다. 마찬가지로 클래스가 인터페이스를 상속한 클래스에 있는 인터페이스 멤버를 상속해 구현할 때도 override 키워드를 추가해야 한다.
interface Vehicle{
fun move() {
println("I'm moving")
}
}
interface Car : Vehicle {
override fun move() {
println("I'm riding")
}
}
위의 예시에서 상위 타입 이름 뒤에 괄호가 없다는 것을 알 수 있다. 이 이유는 인터페이스에는 생성자가 없어서 하위 클래스를 초기화할 때 호출해야 할 코드가 없기 때문이다.
※ 주의
인터페이스가 다른 클래스를 상송할 수 없다는 것을 알고 있어야 한다. Any 클래스만 유일하게 가능하다.
인터페이스가 인터페이스 상속 가능
인터페이스가 클래스 상속 불가
자바와 마찬가지로 코틀린 인터페이스도 다중 상속을 지원한다.
interface Car {
fun ride()
}
interface Plane {
fun fly()
}
interface Boat {
fun sail()
}
interface Vehicle: Car, Plane
class How: Vehicle, Boat {
override fun ride() {
println("I'm riding")
}
override fun fly() {
println("I'm flying")
}
override fun sail() {
println("I'm sailing")
}
}
만약 한 타입이 동일한 시그니처를 가지는 멤버가 들어있는 다른 인터페이스를 둘 이상 상속한다고 가정해보자.
interface Car {
fun move() {
println("I'm moving")
}
}
interface Plane {
fun move()
}
class How: Car, Plane {
override fun move() {
super.move()
}
}
fun main() {
How().move()
}
이런 식으로 구현이 되면 super 호출 자체가 모호해진다. 이런 상황에는 super를 상위 타입으로 한정시킨 키워드를 사용해야 한다.
interface Car {
fun move() {
println("I'm riding")
}
}
interface Plane {
fun move() {
println("I'm flying")
}
}
class How: Car, Plane {
override fun move() {
super<Car>.move()
super<Plane>.move()
}
}
fun main() {
How().move()
}
이렇게 해서 추상 클래스와 인터페이스에 대해 알아보았다.
'Android > Kotlin' 카테고리의 다른 글
Sealed Class (0) | 2023.05.26 |
---|---|
Enum Class (0) | 2023.05.26 |
변수 캡슐화하기 (Encapsulate Variable) (0) | 2023.05.12 |
REST API, Retrofit (0) | 2023.05.01 |
REST API에서 HATEOAS란? (0) | 2023.05.01 |