확장 함수
확장 함수는 어떤 클래스의 멤버 메서드인 것처럼 호출할 수 있지만 그 클래스의 밖에 선언된 함수다. (Kotlin In Action - p.115)
코틀린은 상속이나 데코레이터를 사용하지 않고 클래스를 확장할 수 있는 기능을 제공한다. 확장을 이용하면 수정이 불가능한 서드파티 라이브러리에 대해서도 새로운 함수를 만들 수 있다. 이렇게 만든 함수를 확장 함수라고 하며, 원래 클래스의 메서드인 것처럼 호출할 수 있다.
구현 방법
// this.get(this.length - 1)에서 this 생략
fun String.lastChar(): Char = get(length - 1)
// 호출
"Kotlin".lastChar()
확장 함수를 만드는 방법은 간단하다. 구현하려는 함수 이름 앞에 확장할 클래스의 이름을 붙인다. 클래스 이름을 수신 객체 타입(receiver object type)이라 부르며, 확장 함수가 호출되는 대상이 되는 값(객체)을 수신 객체(receiver object)라 부른다.
확장 함수에서는 this라는 키워드를 사용하여 수신 객체에 접근할 수 있다. 다만 this 키워드는 생략이 가능하므로 생략해서 사용하였다.
적용
Wordle 미션에서는 아래와 같이 확장 함수를 적용했다.
markGreen(), markYellow() 함수는 기존 List<Tile>의 타일을 조건에 맞게 초록색과 노란색으로 교체하여 만든 새로운 리스트를 반환해주는 함수다.
// 확장함수 적용 전의 코드
fun check(word: Word): List<Tile> {
answerMap = initAnswerMap().toMutableMap()
return markYellow(markGreen(DEFAULT_RESULT, word), word)
}
private fun markGreen(result: List<Tile>, word: Word): List<Tile> {
return result.mapIndexed { index, tile -> existOrGreen(tile, word, index) }
}
private fun markYellow(result: List<Tile>, word: Word): List<Tile> {
return result.mapIndexed { index, tile -> existOrYellow(tile, word, index) }
}
// 확장함수를 적용하여 리팩토링한 코드
fun check(word: Word): List<Tile> {
answerMap = initAnswerMap().toMutableMap()
return DEFAULT_RESULT.markGreen(word).markYellow(word)
}
private fun List<Tile>.markGreen(word: Word): List<Tile> {
// this.mapIndexed 에서 this 생략
return mapIndexed { index, tile -> existOrGreen(tile, word, index) }
}
private fun List<Tile>.markYellow(word: Word): List<Tile> {
return mapIndexed { index, tile -> existOrYellow(tile, word, index) }
}
일반 함수로 작성했을 때의 문제점은 가독성이 떨어진다는 것이다. markGreen() 함수는 DEFAULT_RESULT가 인자로 들어가서, markGreen(DEFAULT_RESULT, word) 와 같이 (서술어 + 주어)의 순서로 놓이게 된다. 우리는 일반적으로 (주어 + 서술어)의 순서에 익숙하기 때문에 일반 함수는 가독성이 떨어진다. 또한 기존 코드는 markGreen() 함수의 결과가 markYellow() 함수의 인자로 들어가 있어 함수의 호출 순서를 한눈에 파악하기 힘들다.
// 기존
markYellow(markGreen(DEFAULT_RESULT, word), word)
// 리팩토링 후
DEFAULT_RESULT.markGreen(word).markYellow(word)
확장 함수를 적용하면 DEFAULT_RESULT.markGreen(word).markYellow(word)와 같이 method chaining으로 함수를 이어 쓸 수 있기 때문에 가독성을 높일 수 있다.
주의: 확장 함수는 오버라이드할 수 없다.
먼저 일반적인 멤버 함수를 오버라이드하는 경우를 생각해보자. View와 그 하위 클래스인 Button이 있고, Button이 상위 클래스의 click 함수를 오버라이드하고 있다.
open class View {
open fun click() = println("View clicked")
}
class Button: View() {
override fun click() = println("Button clicked")
View 타입 변수를 선언하더라도 Button 타입의 변수를 그 변수에 대입하면 아래와 같이 Button이 오버라이드한 click이 호출된다.
val view: View = Button()
view.click() // 출력: Button clicked
하지만 확장은 이런식으로 작동하지 않는다. 확장 함수는 클래스의 일부가 아니다. 확장 함수는 클래스 밖에 선언된다. 어떤 확장 함수가 호출될지는 변수의 정적 타입에 의해 결정되지, 변수에 저장된 객체의 동적 타입에 의해 결정되지 않는다.
fun View.showOff() = println("I'm a view!")
fun Button.showOff() = println("I'm a button!")
val view: View = Button()
view.showOff() // 출력: I'm a view!
위 예제와 같이 확장 함수를 오버라이드할 수는 없다. 코틀린은 호출된 확장 함수를 정적으로 결정하기 때문이다.
'코틀린' 카테고리의 다른 글
[Kotlin] Backing Field와 Backing Properties (wordle 미션) (2) | 2022.06.01 |
---|---|
[Kotlin] mutable collection 사용 줄이기 (wordle 미션) (7) | 2022.05.31 |
[Kotlin] Sealed Class (wordle 미션) (0) | 2022.05.31 |