(Kotlin/코틀린) operator fun invoke — 객체를 함수처럼 호출


1. operator fun invoke란?

invoke는 Kotlin에서 객체를 함수처럼 호출할 수 있게 해주는 연산자입니다.
obj(args) 형태의 호출이 내부적으로 obj.invoke(args)로 변환됩니다.

class Greeter {
    operator fun invoke(name: String): String {
        return "안녕하세요, $name!"
    }
}

val greeter = Greeter()
println(greeter("Alice"))          // 안녕하세요, Alice!
println(greeter.invoke("Alice"))   // 동일한 결과

2. 람다와의 관계

Kotlin의 람다와 함수 타입((A) -> B)은 내부적으로 invoke를 가진 객체입니다.

val double: (Int) -> Int = { it * 2 }
println(double(5))         // 10
println(double.invoke(5))  // 10 — 동일

operator fun invoke를 정의하면 클래스 인스턴스가 람다처럼 동작합니다.


3. Android UseCase 패턴

Clean Architecture에서 UseCase를 invoke로 정의하면 호출 코드가 간결해집니다.

// invoke 없는 방식
class GetUserUseCase(private val repository: UserRepository) {
    fun execute(userId: String): User = repository.getUser(userId)
}

// invoke 방식
class GetUserUseCase(private val repository: UserRepository) {
    operator fun invoke(userId: String): User = repository.getUser(userId)
}
// ViewModel에서 호출 비교
class UserViewModel(private val getUser: GetUserUseCase) : ViewModel() {

    // invoke 없음: getUser.execute(id)
    fun loadUser(id: String) {
        val user = getUser.execute(id)
    }

    // invoke 있음: getUser(id) — 함수처럼 자연스럽게 호출
    fun loadUser(id: String) {
        val user = getUser(id)
    }
}

suspend fun invoke — 코루틴 연동

class FetchProductsUseCase(
    private val repository: ProductRepository
) {
    suspend operator fun invoke(category: String): List<Product> =
        repository.getProducts(category)
}

// ViewModel에서
viewModelScope.launch {
    val products = fetchProducts("electronics")  // 함수처럼 호출
    _uiState.value = UiState.Success(products)
}

4. companion object invoke — 팩토리 패턴

companion objectinvoke를 정의하면 생성자처럼 보이지만 더 유연한 팩토리를 만들 수 있습니다.

class HttpClient private constructor(
    val baseUrl: String,
    val timeout: Int,
    val headers: Map<String, String>
) {
    companion object {
        operator fun invoke(
            baseUrl: String,
            timeout: Int = 30,
            headers: Map<String, String> = emptyMap()
        ): HttpClient {
            require(baseUrl.startsWith("http")) { "URL은 http로 시작해야 합니다" }
            return HttpClient(baseUrl, timeout, headers)
        }
    }
}

// 사용: 생성자처럼 보이지만 검증 로직이 내부에 있음
val client = HttpClient("https://api.example.com", timeout = 60)

5. 커맨드 패턴 구현

interface Command {
    operator fun invoke()
}

class SaveCommand(private val data: String, private val repo: Repository) : Command {
    override operator fun invoke() {
        repo.save(data)
        println("저장 완료: $data")
    }
}

class DeleteCommand(private val id: String, private val repo: Repository) : Command {
    override operator fun invoke() {
        repo.delete(id)
        println("삭제 완료: $id")
    }
}

// 커맨드 큐
val commands: List<Command> = listOf(
    SaveCommand("데이터1", repo),
    SaveCommand("데이터2", repo),
    DeleteCommand("old_data", repo)
)

commands.forEach { it() }  // 각 커맨드 실행

6. 상태 기반 invoke

class RateLimiter(private val maxCallsPerSecond: Int) {
    private var callCount = 0
    private var lastResetTime = System.currentTimeMillis()

    operator fun invoke(action: () -> Unit): Boolean {
        val now = System.currentTimeMillis()
        if (now - lastResetTime >= 1000) {
            callCount = 0
            lastResetTime = now
        }
        return if (callCount < maxCallsPerSecond) {
            callCount++
            action()
            true
        } else {
            false
        }
    }
}

val limiter = RateLimiter(3)

repeat(5) { i ->
    val executed = limiter { println("요청 $i 처리") }
    if (!executed) println("요청 $i 차단됨")
}
// 요청 0, 1, 2 처리
// 요청 3, 4 차단됨

7. operator invoke vs 일반 메서드 비교

구분 일반 메서드 operator invoke
호출 문법 obj.method(args) obj(args)
람다 대체 어색함 자연스러움
고차 함수 전달 메서드 참조 필요 (::method) 객체 자체 전달 가능
상태 보관 가능 가능
주요 용도 일반 객체 메서드 UseCase, 커맨드, 팩토리
// 고차 함수에 전달 시 차이
class Validator {
    fun validate(s: String) = s.isNotEmpty()
    // operator fun invoke(s: String) = s.isNotEmpty()
}

val validator = Validator()

// 일반 메서드: 메서드 참조 필요
listOf("a", "", "b").filter(validator::validate)

// invoke: 객체 자체를 전달 가능
listOf("a", "", "b").filter(validator)  // operator invoke 있으면 가능

8. 정리



Related Posts