-
[코틀린] 델리게이션코틀린 2022. 3. 16. 23:50
델리게이션은 객체 자신이 처리해야 할 작업을 다른 객체에게 넘겨버리는 개념이다.
1. 델리게이션
아래 예시는 TrainBookAgent가 실제로 해야할 작업을 각각의 TicketBooker로 위임하는 것을 보여준다.
interface TicketBooker { fun book() fun isAvailable() } class TrainTicketBooker: TicketBooker { override fun book() { println("Book Train ticket") } override fun isAvailable() { println("Train ticket available") } } class AirplaneTicketBooker: TicketBooker { override fun book() { println("Book Airplane ticket") } override fun isAvailable() { println("Airplane ticket available") } } class TicketBookAgent(private val booker: TicketBooker): TicketBooker { override fun book() { booker.book() } override fun isAvailable() { booker.isAvailable() } } val airplaneAgent = TicketBookAgent(AirplaneTicketBooker()) val trainTicketBooker = TicketBookAgent(TrainTicketBooker()) airplaneAgent.book() // Book Airplane ticket trainTicketBooker.book() // Book Train ticket
위의 방식은 코드의 중복이 발생해서 다른 메소드가 추가 될 때 TicketBookAgent 클래스도 같이 변경해주어야 하는 단점이 있다.
코틀린은 이런 델리게이션을 문법적으로 쉽게 할 수 있도록 지원한다.
코틀린의 by 키워드를 이용한 델리게이션
class AgentByDelegation(): TicketBooker by AirplaneTicketBooker() val agent1 = AgentByDelegation() agent1.book() // Book Airplane ticket
위의 클래스 선언은 by 키워드를 이용해서 AirplainTicketBooker 인스턴스에 작업을 위임하는 예시를 보여준다.
아래와 같이 선언해서 AirplaneTicketBooker에 한정하지 않고 TicketBooker타입을 받아 처리하도록 만들 수도 있다.
class TicketBookAgentUsingDelegation(val booker: TicketBooker): TicketBooker by booker val agent2 = TicketBookAgentUsingDelegation(AirplaneTicketBooker()) agent2.book() //Book Airplane ticket
메소드 충돌 관리
델리게이션을 한 상태에서 위임한 클래스와 같은 이름의 메소드를 정의하면 이때는 원래 클래스의 메소드가 실행된다.
class SuperTicketBookAgent(): TicketBooker by AirplaneTicketBooker() { override fun book() { println("Book First class ticket") } } SuperTicketBookAgent().book() // Book First class ticket
두 개 이상의 델리게이션
티켓을 예약해주는 TicketBooker에 현지 가이드를 예약해주는 Guide가 추가되어 그 둘에게 동시에 작업을 위임하는 Agency가 있다고 가정하고 이것을 델리게이션으로 구현해보자
interface Guide { fun connect() fun isAvailable() } class CastleGuide: Guide { override fun connect() { println("Castle Guide connected") } override fun isAvailable() { println("Castle Guide available") } } class TotalTravelAgency(): TicketBooker by AirplaneTicketBooker(), Guide by CastleGuide() { override fun isAvailable() { println("lets check all available") } }
이 때 isAvailable과 같이 두 인터페이스에서 같은 이름의 메소드를 사용중일 수 있다. 이런 메소드는 반드시 오버라이드를 해주어야한다.
2. 델리게이션 활용
변수 델리게이션
델리게이션은 변수를 읽고 쓰는데에도 적용할 수 있다.
import kotlin.reflect.KProperty class GetterThief(var content: String) { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { println("content will be returned") return content } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$value is stolen. value will be gone") content = "gone" } } var strExample: String by GetterThief("my first message") println(strExample) // content will be returned\n myfirstmessage strExample = "Hi" // Hi is stolen. value will be gone println(strExample) //content will be returned\n gone
내부적으로 변수를 읽어오는데 getValue, 변수를 할당하는데 setValue를 사용하는데 이 메소드를 정의한 클래스로 변수 읽기 쓰기를 위임했기 때문에 위와 같은 결과가 출력된다.
속성 델리게이션
지역 변수 뿐만 아니라 객체 속성에도 델리게이션을 활용할 수 있다. 코틀린의 Map, MutableMap에는 get과 함께 getValue, set과 함께 setValue를 가지고 있다.
class Translator(val _map: MutableMap<String, String>) { operator fun getValue(thisRef: Any?, property: KProperty<*>) = (_map[property.name])?.replace("apple", "사과") ?: "" operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { _map[property.name] = value } } val translator = mutableMapOf("name" to "apple") class Fruit(translator: MutableMap<String, String>) { val name: String by Translator(translator) } val apple = Fruit(translator) println(apple.name) // 사과
name속성을 읽으면 Translator의 getValue가 호출되어 property로 name이 넘어가고, 그 결과로 사과가 출력된다.
빌트인 델리게이션
코틀린은 기본적으로 몇가지 빌트인 델리게이션을 제공한다.
lazy
어떤 함수의 계산을 꼭 필요할때까지 미룬다.
fun getTemperatureFromOthers(): Int { println("get temperature called") sleep(1000) return 20 } val showTemperature = false val temperature = getTemperatureFromOthers() if(showTemperature && temperature > 10) println("Long sleeve") else println("I dont know") // 출력 // get temperature called // Long sleeve
위의 경우에는 무조건 getTemperaturFromOthers가 실행되기 때문에 두 줄이 출력된다.
아래는 showTemperature 가 false 이므로 temperature에 대한 조회가 일어나지 않아 lazy로 감싼 함수의 호출도 일어나지 않는다.
fun getTemperatureFromOthers(): Int { println("get temperature called") sleep(10000) return 20 } val showTemperature = false //val temperature = getTemperatureFromOthers() val temperature by lazy { getTemperatureFromOthers() } if(showTemperature && temperature > 10) println("Long sleeve") else println("I dont know")
observable
옵저버블은 변수에 변화가 발생하면 observable() 함수에 등록한 핸들러를 호출한다.
fun callCountChanged() { println("count chaned!") } var count by Delegates.observable(0) { property: KProperty<*>, oldValue: Int, newValue: Int -> callCountChanged() } count++ // count changed!
vetoable
vetoable은 변경을 거부할 수 있다.
var toVeto by Delegates.vetoable(0) { property: KProperty<*>, oldValue: Int, newValue: Int -> newValue <= 0 } toVeto += 100 println("toVeto: $toVeto") // toVeto: 0
'코틀린' 카테고리의 다른 글
[코틀린] 내부 반복(map, filter, groupBy, reduce) (0) 2022.03.19 [코틀린] 람다 (0) 2022.03.19 [코틀린] 클래스와 상속 (0) 2022.03.13 [코틀린] 객체와 클래스 (0) 2022.03.13 [코틀린] 타입 안정성 (0) 2022.03.07