Summary
- 행동을 나타내기 위해, 표준 인터페이스나 추상 클래스 대신 함수 타입이나 함수형 인터페이스를 선호합시다.
- 함수 타입이 좀 더 자주 사용 됩니다. 여러 번 사용되거나 타입이 길어지면 type alias를 사용합시다.
- 함수형 인터페이스는 주로 Java 상호 운용성을 위해 선호되며, 표현하려는 것이 임의의 함수 이상으로 복잡한 경우에 선호됩니다.
(요약만 봐서는 사실 잘 이해가 안된다 살펴보자!)
Intro
많은 언어에는 fuction type이라는 개념이 없습니다. 대신 하나의 메소드를 갖는 interface를 사용합니다.
이런 인터페이스를 SAM(Single-Abstract Method)이라고 합니다.
아래 예시는 view를 클릭할 때 발생해야 하는 정보를 전달하는데 사용되는 SAM의 예입니다.
interface OnClick {
fun onClick(view: View)
}
함수가 SAM이라면, 이 인터페이스를 구현하는 객체의 인스턴스를 전달해야 합니다.
fun setOnClickListener(listener: OnClick) {
//..
}
setOnClickListener(object: OnClick {
override fun onClick(view: View) {
//...
}
})
하지만 코틀린에서는 보다 자유를 주는 2가지 메커니즘을 제공합니다.
- function type (함수 타입)
- functional interface (함수형 인터페이스)
// Function type 사용
fun setOnClickListener(listener: (View) -> Unit) {
// ...
}
// Functional interface 선언
fun interface OnClick {
fun onClick(view: View)
}
// Functional interface 사용
fun setOnClickListener(listener: OnClick) {
//..
}
위 두가지 중 하나를 사용할 때, argument는 다음과 같이 정의할 수 있습니다.
- lamabda 식 또는 익명 함수
setOnClickListener { /*...*/ }
setOnClickListener(fun(view) { /*...*/ })
- 함수 참조 또는 제한된 함수 참조
setOnClickListener(::println)
setOnClickListener(this::showUsers)
- 선언된 함수 type 나 함수형 인터페이스를 구현하는 객체
class ClickListener: (View)->Unit {
override fun invoke(view: View) {
//...
}
}
setOnClickListener(ClickListener())
type aliases와 함께 function types 사용하기
함수 타입이 복잡해지면, type aliase(타입 별칭)을 사용하여 이름을 정할 수 있습니다.
typealias OnClick = (View) -> Unit
type alias는 "별명"처럼 타입에 대한 다른 이름을 제공합니다.
컴파일 시간 동안 type alias는 그들이 나타내는 유형으로 대체됩니다. 이 type alias는 제네릭이 될 수도 있습니다.
typealias OnClick<T> = (T) -> Unit
함수 타입 매개변수는 이름을 지을 수 있습니다.
매개 변수 이름을 지정하기 시작하면 타입이 길어지는 경향이 있는데,
type alias와 함께 사용하면 가독성을 높일 수 있고, IDE에서 함수를 사용할 때 이름을 제안해주기도 합니다.
typealias OnClick = (view: View) -> Unit
fun setOnClickListener(listener: OnClick) { /*...*/ }
functional interfaces (함수형 인터페이스)를 사용하는 이유
함수형 인터페이스는 더 무거운 방법(?!)입니다.
인터페이스를 정의해야 하지만, 그 대신
- 함수형 인터페이스는 새로운 이름의 타입을 정의합니다.
- 핸들러 함수는 다르게 이름을 정할 수 있습니다. (함수 유형에서는 항상 호출)
- 다른 언어와의 상호 운용성이 더 좋습니다.
interface SwipeListener {
fun onSwipe()
}
fun interface FlingListener {
fun onFling()
}
fun setOnClickListener(listener: SwipeListener) {
listener.onSwipe()
}
fun main() {
val onSwipe = SwipeListener { println("Swiped") }
setOnClickListener(onSwipe) // Swiped
val onFling = FlingListener { println("Touched") }
setOnClickListener(onFling) // Error: Type mismatch
}
함수형 인터페이스는 non-abstract 함수를 추가할 수 있고, 다른 인터페이스를 구현할 수도 있습니다.
interface ElementListener<T> {
fun invoke(element: T)
}
fun interface OnClick: ElementListener<View> {
fun onClick(view: View) //Non-abstract function
fun invoke(element: View) { //다른 인터페이스 구현
onClick(element)
}
}
Kotlin이 아닌 다른 언어에서 사용할 클래스를 설계할 때, 인터페이스가 더 깨끗하고 더 잘 지원됩니다.
다른 언어는 type alias나 이름 제안을 볼 수 없습니다.
일부 언어(특히 Java)에서 사용할 때는, Kotlin 함수 타입을 사용하기 위해 함수에서 Unit을 명시적으로 반환해야 합니다.
//Kotlin
class CalenderView() {
var onDateClicked: ((date: Date) -> Unit)? = null
var onPageChanged: OnDateClicked? = null
}
fun interface OnDateClicked {
fun onClick(date: Date)
}
//Java
CalendarView c = new CalendarView();
c.setOnDateClicked(date -> Unit.INSTANCE);
c.setOnPageChanged(date -> {});
전반적으로 함수형 인터페이스가 더 선호되는 이유는 다음과 같습니다.
- Java 상호 운용성
- 기본형 타입(primitive type)에 대한 최적화
- 단순히 함수를 표현하는 것이 아닐 때, 구체적인 계약을 표현해야 함
함수형 인터페이스는 Kotlin 1.4에서 도입되었으며, 현재 함수 타입에 대한 IntelliJ의 지원은 함수형 인터페이스보다 낫습니다.
여러 추상 메서드가 있는 인터페이스를 사용해 action을 표현하지 마세요
Java에서 Kotlin으로 전환한 개발자들 사이에서 관찰되는 또 다른 특징은
여러 추상 메서드가 있는 인터페이스를 사용하여 action을 표현한다는 것입니다.
class CalendarView {
var listener: Listener? = null
interface Listener {
fun onDateClicked(date: Date)
fun onPageChanged(date: Date)
}
}
이런 패턴은 함수형 인터페이스가 지원되지 않았을 때 Java에서 널리 사용되었습니다.
API를 사용하는 관점에서는 함수 타입이나 함수형 인터페이스를 보유하는 별도의 속성으로 설정하는 것이 좋습니다.
//함수형 인터페이스 사용
class CalendarView {
var onDateClicked: OnDateClicked? = null
var onPageChanged: OnPageClicked? = null
}
//함수 타입 사용
class CalendarView {
var onDateClicked: ((date: Date) -> Unit)? = null
var onPageChanged: ((date: Date) -> Unit)? = null
}
이렇게 하면 onDateClicked 및 onPageChanged 구현을 인터페이스에서 함께 묶을 필요가 없습니다.
이런 기능을 독립적으로 변경할 수 있으며, lambda식과 기타 모든 옵션을 사용해 설정할 수 있습니다.
댓글