어떤 클래스에 대한 확장 함수를 정의할 때, 이를 멤버로 추가하는 것은 좋지 않습니다. 확장 함수는 첫 번째 아규먼트로 리시버를 받는 단순한 일반 함수로 컴파일 됩니다.
예를 들어 다음과 같은 함수는
fun String.isPhoneNumber(): Boolean =
length == 7 && all { it.isDigit() }
컴파일 되면 다음과 같이 변합니다.
fun isPhoneNumber('$this': String): Boolean =
'$this'.length == 7 && '$this'.all { it.isDigit() }
이렇게 단순하게 변화되는 것이므로, 확장 함수를 클래스 멤버로 정의할 수도 있고, 인터페이스 내부에 정의할 수 도 있습니다.
interface PhoneBook {
fun String.isPhoneNumber(): Boolean
}
class Fizz: PhoneBook {
override fun String.isPhoneNumber(): Boolean {
return length == 7 && all { it.isDigit() }
}
}
이런 코드가 가능하지만, DSL을 만들 때를 제외하면 이를 사용하지 않는 것이 좋습니다. 특히 가시성 제한을 위해 확장 함수를 멤버로 정의하는 것은 굉장히 좋지 않습니다.
확장 함수의 가시성을 제한하고 싶다면, 멤버로 만들지 말고, 가시성 한정자를 붙여주면 됩니다.
class PhoneBookCorrect {
// ...
}
private fun String.isPhoneNumber() =
length == 7 && all { it.isDigit() }
멤버 확장을 피해야 하는 이유는 아래와 같습니다.
1. 레퍼런스를 지원하지 않습니다.
class PhoneBookIncorrect {
// ...
fun String.isPhoneNumber() =
length == 7 && all { it.isDigit() }
}
val ref = String::isPhoneNumber
val str = "1234567890"
val boundRef = str::isPhoneNumber
val refX = PhoneBookIncorrect::isPhoneNumber // 오류
val book = PhoneBookIncorrect()
val boundRefX = book::isPhoneNumber // 오류
2. 암묵적 접근을 할 때, 두 리시버 중에 어떤 리시버가 선택될지 혼동됩니다.
class A {
val a = 10
}
class B {
val a = 20
val b = 30
fun A.test() = a + b // 40? or 50?
}
3. 확장 함수가 외부에 있는 다른 클래스를 리시버로 받을 때, 해당 함수가 어떤 동작을 하는지 명확하지 않습니다.
class A {
// ...
}
class B {
// ...
fun A.update() = ... // A와 B 중에서 어떤 것을 업데이트할까요?
}
4. 경험이 적은 개발자의 경우 확장 함수가 직관적이지 않을 수 있습니다.
'Kotlin' 카테고리의 다른 글
[Effective Kotlin] 46 - 함수 타입 파라미터를 갖는 함수에 inline 한정자를 붙여라 (1) | 2023.12.10 |
---|---|
[Effective Kotlin] 45 - 불필요한 객체 생성을 피하라 (1) | 2023.12.04 |
[Effective Kotlin] 43 - API의 필수적이지 않는 부분을 확장 함수로 추출하라 (0) | 2023.11.19 |
[Effective Kotlin] 42 - compareTo의 규약을 지켜라 (0) | 2023.11.12 |
[Effective Kotlin] 41 - hashCode의 규약을 지켜라 (0) | 2023.11.04 |