1. 추상 클래스
여러 클래스를 한 타입으로 묶어주고, 공통되는 멤버를 전파하는 용도로 쓰는 클래스는 추상 클래스로 선언하는 것이 좋다.
코틀린에서 클래스를 추상 클래스로 만드려면 클래스 선언 맨 앞에 abstract 키워드를 붙이면 된다.
abstract 키워드는 그 자체로 open을 포함하고 있기 때문에 open 키워드를 따로 적지 않아도 된다.
추상 클래스는 추상 멤버 함수와 추상 프로퍼티도 가질 수 있다.
멤버 함수 또는 추상 프로퍼티 맨 앞에 abstract 키워드를 붙이면 된다.
* 추상 멤버 함수란? 내용이 없는 멤버 함수
추상 클래스를 상속하는 서브 클래스에도 abstract를 붙이면 추상 멤버 함수를 반드시 오버라이딩 하지 않아도 된다.
단, 이 클래스를 다시 일반 클래스로 상속할 때는 오버라이딩을 반드시 해야 한다.
// 단순히 학생, 교수, 직원 클래스를 하나의 타입으로 묶어주는 클래스 - 추상클래스
abstract class Person {
abstract fun getSalary(): Int
}
// 학생클래스. tuition은 한 학기 등록금
class Student(private val tuition: Int): Person() {
// 학생은 등록금을 납부하므로 salary를 음수 처리
override fun getSalary() = -tuition
}
// 교수 클래스. classCount는 진행하는 수업의 수
class Professor(private val classCount: Int): Person() {
override fun getSalary() = classCount * 200
}
// 학교 직원 클래스. initial은 초봉(반기), years는 경력(년)
class Employee(private val initial: Int, private val year: Int): Person() {
override fun getSalary() = initial * (1.0 + year / 10.0).toInt()
}
// 구성원으로부터 학교의 재정을 구한다.
fun getFinance(vararg persons:Person): Int {
var i = 0
var finance = 0
while(i < persons.size) {
finance -= persons[i].getSalary()
i += 1
}
return finance
}
fun main() {
// Student, Professor, Employee 타입이 상속 관계에 의해 Person 타입으로 묶여있으므로 해당 변수를 여러개 받을 수 있다.
val finance = getFinance(Student(330), Student(330), Professor(1), Professor(2), Employee(1300, 2))
println("학교 재정: $finance 만원")
}
2. 인터페이스
인터페이스는 클래스에 어떤 멤버 함수와 프로퍼티가 반드시 존재한다는 것을 보장하기 위한 장치이다.
인터페이스는 기존의 클래스를 확장한다는 것보다는, 어떤 클래스에 플러그인을 추가한다는 개념에 가깝다.
class 키워드 대신 interface 키워드 사용하여 인터페이스를 선언하면 된다.
인터페이스는 멤버 함수, 추상 멤버 함수, 추상 프로퍼티를 가질 수 있으며, 일반 프로퍼티와 생성자는 가질 수 없다.
interface Printable {
// print 멤버 함수 내용이 비어있으면 자동으로 abstract가 붙는다.
fun print(): Unit
}
// AAA 클래스가 Printable 인터페이스를 구현
// 인터페이스는 생성자가 존재하지 않기 때문에 이름 옆에 () 쓰지 않음
class AAA: Printable {
override fun print() {
println("Hello")
}
}
// Printable 타입의 인수를 받는 print 함수 선언
fun print(anything: Printable) {
// 매개변수 타입이 Printable이므로 그 매개변수가 가리키는 객체에 print 함수가 들어있다는 것을 항상 보장
anything.print()
}
fun main() {
print(AAA()) // Hello
}
3. 다이아몬드 문제
interface Parent{
fun follow(): Unit
}
interface Mother: Parent{
override fun follow() = println("follow his mother")
}
interface Father: Parent{
override fun follow() = println("follow his father")
}
class Child: Mother, Father {
override fun follow() {
println("A child decided to ")
super.follow()
}
}
위 상속, 구현 관계를 그림으로 표현하면 다음과 같다.
상속 관계 그림이 다이아몬드를 닮아서 다이아몬드 문제라는 이름이 붙었다.
위 코드의 문제점은 follow 추상 멤버 함수를 갖는 Parent 인터페이스가 있고, Mother와 Father 인터페이스에서 follow를 오버라이딩하고 있다. Child 클래스는 Mother, Father 인터페이스를 모두 구현하고 있는데 super.hello()를 하면 Mother과 Father 중 어떤 인터페이스의 follow가 호출될지 애매하다.
코틀린은 이런 상황을 위해서 원하는 인터페이스의 super를 호출할 수 있는 기능을 제공한다.
interface Parent{
fun follow(): Unit
}
interface Mother: Parent{
override fun follow() = println("follow his mother")
}
interface Father: Parent{
override fun follow() = println("follow his father")
}
class Child: Mother, Father {
override fun follow() {
println("A child decided to ")
super<Mother>.follow()
}
}
fun main() {
Child().follow()
/*
A child decided to
follow his mother
*/
}
<>로 호출할 super 멤버 함수를 지정하고 있다.
위의 코드에서 살펴보면 Child 클래스 안에서 super<Mother>.follow()로 지정하여
main에서 child().follow()를 호출하면 Mother의 follow 멤버 함수를 호출한다.
'TIL > Kotlin' 카테고리의 다른 글
[TIL/Kotlin] 코틀린 고급문법_데이터 클래스와 객체 분해하기 (0) | 2023.05.21 |
---|---|
[TIL/Kotlin] 코틀린 고급문법_중첩 클래스와 내부 클래스 (0) | 2023.05.21 |
[TIL/Kotlin] 코틀린 고급문법_Nullable 리시버, 동반자 객체의 확장 함수, 확장 함수의 리시버 타입이 상속 관계에 있을 때 참조 변수를 따른다 (0) | 2023.05.17 |
[TIL/Kotlin] 코틀린 고급문법_inline 함수, const, lateinit (0) | 2023.05.17 |
[TIL/Kotlin] 코틀린 고급문법_객체 선언과 동반자 객체 (0) | 2023.05.17 |