<오버라이딩 overriding>
상속시에는 기본적으로 수퍼 클래스에 있는 함수와 같은 이름과 형식을 가진 함수는 서브 클래스에서 만들 수 없습니다
하지만 수퍼 클래스에서 허용만 한다면 오버라이딩이라는 방법으로 서브 클래스에서 같은 이름과 형태로 된 함수의 내용을
다시 구현할 수 있습니다
예시를 통해 오버라이딩을 보겠습니다
fun main() {
var t = Tiger()
t.eat()
}
open class Animal {
open fun eat() {
println("음식을 먹습니다")
}
}
class Tiger : Animal() {
override fun eat() {
println("고기를 먹습니다")
}
}
/* 출력
고기를 먹습니다
*/
Tiger 클래스는 Animal 클래스를 상속받고 있습니다
이때 Tiger 클래스에서는 eat 함수를 호출하면 "음식을 먹습니다" 대신 "고기를 먹습니다" 라고 출력이 하고 싶습니다
그렇다면 Animal 클래스에 있는 eat 함수를 오버라이딩하여 사용해야 하는데요
이때도 Animal 클래스에 있는 eat 함수가 open 으로 되어 있어야 Tiger에서 재구현이 허용됩니다
수퍼클래스에서 'open'이 붙은 함수는 서브클래스에서 'override'를 붙여 함수를 재구현 할 수 있습니다
Tiger 클래스에서 함수를 오버라이드 하여 작성한 후 t 인스턴스에서 eat 함수를 사용하면 재구현된 내용인
"고기를 먹습니다" 가 출력되는 것을 확인할 수 있습니다
위에선 이미 수퍼클래스에서 구현이 끝난 함수를 오버라이딩을 통해 재구현하는 경우였는데요
오버라이딩과 다르게 수퍼클래스에서는 함수의 구체적인 구현은 없고 단지 Animal의 모든 서브클래스는
eat이라는 함수가 '반드시 있어야 한다'는 점만 명시하여 각 서브클래스가 비어있는 함수의 내용을 필요에 따라 구현하도록 하려면
추상화라는 개념을 사용해야 합니다
<추상화 abstraction>
추상화는 선언부만 있고 기능이 구현되지 않은 추상 함수 abstraction function,
추상 함수를 포함하는 추상클래스 abstraction class 라는 요소로 구성됩니다
추상함수를 포함한 추상클래스를 만들고 서브클래스에서 상속받아 구현하는 예시 코드를 보도록 하겠습니다
fun main() {
var r = Rabbit()
r.eat()
r.sniff()
}
abstract class Animal {
abstract fun eat()
fun sniff() {
println("킁킁")
}
}
class Rabbit : Animal() {
override fun eat() {
println("당근을 먹습니다")
}
}
/* 출력
당근을 먹습니다
킁킁
*/
Animal 클래스를 만들고 그 앞에 abstract를 붙여줍니다
그리고 추상함수인 eat 앞에도 abstract를 붙이고 함수의 내용은 적지 않습니다
* 추상함수는 비어있는 껍데기라고 생각하면 쉽습니다!
그리고 "킁킁"을 출력하는 sniff 함수도 추가해주었습니다
그런데 위처럼 abstract를 붙인 추상클래스는 일부 함수가 구현되지 않은 '미완성 클래스'이기 때문에
단독으로는 인스턴스를 만들 수 없습니다
따라서 반드시 서브클래스에서 상속을 받아 abstract 표시가 된 함수들을 구현해줘야 합니다!
그래서 Rabbit이라는 클래스를 만들어 Animal 클래스를 상속 받고
eat이라는 추상함수의 실제 동작이 되는 구현부를 만들어주었습니다
그리고 Rabbit의 인스턴스를 만들어 eat과 sniff를 수행하도록 하면
Rabbit 클래스에서 작성한 eat 함수와 Animal 클래스에 있는 sniff 함수가 실행되어
당근을 먹습니다
킁킁
이 출력되는 것을 확인할 수 있습니다
위 방법 뿐만 아니라 추상화를 하는 또 다른 방법이 있는데요
바로 인터페이스 interface라는 기능입니다!
<인터페이스 interface>
원래 인터페이스는 추상함수로만 이루어져 있는 '순수 추상화 기능'을 말하는 것이라고 많이 알려져있는데요
코틀린에서는 인터페이스 역시 추상함수와 일반함수 모두를 가질 수 있습니다
그리고 추상클래스보다 추상화가 더 높습니다
다만 추상함수는 생성자를 가질 수 있는 반면
인터페이스는 생성자를 가질 수 없으며
구현부가 있는 함수는 open 함수로 간주하고,
구현부가 없는 함수는 abstract 함수로 간주하기 때문에
별도의 키워드가 없어도 포함된 모든 함수를 서브클래스에서 구현 및 재정의가 가능합니다
또한 한번에 여러 인터페이스를 상속받을 수 있으므로 더 유연한 설계가 가능합니다
인터페이스 2개를 한번에 상속받는 클래스 예제를 보도록 하겠습니다
fun main() {
var d = Dog()
d.run()
d.eat()
}
interface Runner { // 인터페이스 Runner
fun run() // 구현부 없는 함수 run
}
interface Eater { // 인터페이스 Eater
fun eat() { // 구현부 있는 함수 eat
println("음식을 먹습니다")
}
}
class Dog : Runner, Eater { // 인터페이스 Runner, Eater를 상속받은 클래스 Dog
override fun run() {
println("우다다다 뜁니다")
}
override fun eat() {
println("허겁지겁 먹습니다")
}
}
/* 출력
우다다다 뜁니다
허겁지겁 먹습니다
*/
Runner라는 인터페이스는 구현부가 없는 함수 run()을 가지고 있고
Eater라는 인터페이스는 "음식을 먹습니다"를 출력하는 eat() 함수를 가지고 있습니다
그리고 Dog 클래스 선언 뒤에 콜론(:)을 붙여 Runner와 Eater 인터페이스를 상속받았습니다
구현부가 없던 run 함수에는 override를 붙여 "우다다다 뜁니다"를 출력하도록 구현해주고
이미 구현이 있는 eat 함수에는 override를 붙여 재구현을 해주었습니다
이렇게 하면 Dog 클래스는 두 인터페이스의 형식들을 모두 물려받아 사용하는 서브클래스가 됩니다
이때 주의해야 할 점은 여러개의 인터페이스나 클래스에서 같은 이름과 형태를 가진 함수를 구현하고 있다면
서브클래스에서는 혼선이 일어나지 않도록 반드시 오버라이딩하여 재구현 해주어야 합니다
💡오버라이딩, 추상화, 인터페이스를 정리해보자면
오버라이딩은 이미 구현이 끝난 함수의 기능을 서브클래스에서 변경해야 할 때,
그리고 추상화는 형식만 선언하고 실제 구현은 서브클래스에 일임할 때 사용하는 기능이며
인터페이스는 서로 다른 기능들을 여러개 물려주어야 할 때 유용한 기능입니다
<오버라이딩과 추상화> 포스팅은 여기서 마치도록 하겠습니다
*위 글은 유튜브 테크과학! DeMo님의 강좌를 참고하여 작성하였습니다
'Kotlin' 카테고리의 다른 글
[Kotlin] 변수, 함수, 클래스의 접근범위와 접근제한자 (0) | 2023.07.24 |
---|---|
[Kotlin] 코틀린 프로젝트의 구조 (0) | 2023.07.23 |
[Kotlin] 09. 클래스의 상속 (0) | 2023.07.19 |
[Kotlin] 08. 클래스의 생성자 (0) | 2023.07.18 |
[Kotlin] 07. 클래스의 기본 구조 (0) | 2023.07.18 |