ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [코틀린] 델리게이션
    코틀린 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

    댓글

Designed by Tistory.