확장 함수
확장 함수 (Extension Functions)
이 장에서는 코드를 더 간결하고 읽기 쉽게 만들어 주는 Kotlin의 특별한 함수들을 살펴볼게요. 효율적인 디자인 패턴을 활용해 프로젝트의 수준을 한 단계 끌어올리는 방법도 배워 볼 거예요.
출처: Kotlin 공식문서
본문
확장 함수 (Extension functions)
소프트웨어 개발을 하다 보면 원본 소스 코드를 바꾸지 않고 프로그램의 동작을 수정해야 할 때가 많아요. 예를 들어 서드파티 라이브러리의 클래스에 기능을 하나 더 추가하고 싶을 수 있죠.
이럴 때 **확장 함수(extension function)**를 추가해 클래스를 확장하면 돼요. 확장 함수는 클래스의 멤버 함수를 호출하듯이, 마침표 .를 이용해 똑같이 호출해요.
확장 함수의 전체 문법을 소개하기 전에 **리시버(receiver)**가 무엇인지 먼저 알아야 해요. 리시버는 함수가 호출되는 대상이에요. 다시 말해, 정보를 공유하는 주체 또는 대상이 리시버예요.
아래 예시에서 main() 함수는 .first() 함수를 호출해 리스트의 첫 번째 요소를 돌려받아요. .first() 함수는 readOnlyShapes 변수에서 호출되므로, readOnlyShapes 변수가 리시버예요.
확장 함수를 만들려면 확장하고 싶은 클래스의 이름을 쓰고, 그다음 .과 함수 이름을 적어요. 그리고 함수의 나머지 선언(파라미터와 반환 타입)을 이어서 작성하면 돼요.
예를 들어:
fun String.bold(): String = "<b>$this</b>"
fun main() {
// "hello" is the receiver
println("hello".bold())
// <b>hello</b>
}
이 예시에서:
String은 확장된 클래스예요.bold는 확장 함수의 이름이에요..bold()확장 함수의 반환 타입은String이에요.String의 인스턴스인"hello"가 리시버가 돼요.- 리시버는 함수 본문 안에서
this키워드로 접근돼요. $문자열 템플릿이this의 값을 접근하는 데 사용돼요..bold()확장 함수는 문자열을 받아 굵은 글씨를 위한<b>HTML 요소로 감싸 돌려줘요.
확장 지향 설계 (Extension-oriented design)
확장 함수는 어디에서든 정의할 수 있어서, **확장 지향 설계(extension-oriented design)**를 만들 수 있어요. 이 설계는 핵심 기능을 유용하지만 필수적이지 않은 부가 기능과 분리해서, 코드를 더 읽기 쉽고 유지보수하기 쉽게 만들어 줘요.
좋은 예시로, 네트워크 요청을 수행하는 데 도움을 주는 Ktor 라이브러리의 HttpClient 클래스가 있어요. 이 클래스의 핵심 기능은 HTTP 요청에 필요한 모든 정보를 받는 request() 함수 하나예요:
class HttpClient {
fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {
// Network code
}
}
실제로 가장 많이 쓰는 HTTP 요청은 GET이나 POST예요. 라이브러리가 이런 흔한 사용 사례에 더 짧은 이름을 제공하면 자연스럽겠죠. 그런데 이런 것들은 새 네트워크 코드를 작성할 필요가 없고, 특정 요청 호출만 하면 돼요. 다시 말해, 별도의 .get()과 .post() 확장 함수로 정의하기에 딱 좋은 후보예요:
fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())
fun HttpClient.post(url: String): HttpResponse = request("POST", url, emptyMap())
이 .get()과 .post() 함수는 HttpClient 클래스를 확장해요. 이 함수들은 HttpClient 클래스의 인스턴스를 리시버로 해서 호출되기 때문에, HttpClient 클래스의 request() 함수를 바로 사용할 수 있어요. 이 확장 함수들을 쓰면 적절한 HTTP 메서드로 request() 함수를 호출할 수 있어서, 코드가 단순해지고 이해하기 쉬워져요:
class HttpClient {
fun request(method: String, url: String, headers: Map<String, String>): HttpResponse {
println("Requesting $method to $url with headers: $headers")
return HttpResponse("Response from $url")
}
}
fun HttpClient.get(url: String): HttpResponse = request("GET", url, emptyMap())
fun main() {
val client = HttpClient()
// Making a GET request using request() directly
val getResponseWithMember = client.request("GET", "https://example.com", emptyMap())
// Making a GET request using the get() extension function
// The client instance is the receiver
val getResponseWithExtension = client.get("https://example.com")
}
이런 확장 지향 방식은 Kotlin의 표준 라이브러리와 다른 라이브러리들에서 널리 사용돼요. 예를 들어 String 클래스에는 문자열을 다루는 데 도움을 주는 확장 함수가 아주 많아요.
확장 함수에 대한 자세한 내용은 확장(Extensions) 문서를 참고하세요.
연습 (Practice)
연습 1
정수를 받아 그 수가 양수인지 확인하는 isPositive라는 확장 함수를 작성해 보세요.
fun Int.// Write your code here
fun main() {
println(1.isPositive())
// true
}
연습 2
문자열을 받아 소문자 버전을 돌려주는 toLowercaseString이라는 확장 함수를 작성해 보세요.
String 타입에 대해 .lowercase() 함수를 사용하세요.
fun // Write your code here
fun main() {
println("Hello World!".toLowercaseString())
// hello world!
}