Kotlin

Kotlin Delegation 알아보기(1) - Delegated Class

MJ핫산 2023. 2. 16. 16:57

안드로이드 개발을 하면서 by lazy {} , by remember {} 등 by 키워드를 자주 사용하고 있었다. 그런데 by 키워드가 Kotlin Delegation을 위한 문법이라는 것을 알고 있었지만, Delegation(위임)이라는 것에 대해 제대로 알고 있지 않다는 생각이 들어서 이번 기회에 알아보려고 한다.

👀 어떨 때 상속 대신 위임을 써야할까?

상속과 위임은 모두 객체지향 프로그래밍에서 사용되는 디자인 패턴으로, 두 방식 모두 클래스를 다른 클래스로부터 확장한다. 차이점을 살펴보자면 상속은 해당 클래스에 귀속되는 것이기 때문에 클래스가 다른 클래스들 사이에서 선택할 권한을 주지 않는다. 반면 위임은 객체 자신이 처리해야 할 일을 다른 클래스 인스턴스에게 위임할 수 있는 것이기 때문에 상속보다 유연하다. 그래서 여러 객체지향 도서에서 상속(is-a)보다 위임(has-a)을 사용할 것을 권장하고 있다고 한다. 책 좀 읽어야겠다…

하지만 그렇다고 상속이 나쁜 것은 아니고 각자 적절한 곳에 사용하면 되는데, 그 기준에 대해서 Ready Kim 님이 정리해주신게 있어서 가져와봤다…

  • 클래스의 객체가 다른 클래스의 객체가 들어갈 자리에 쓰여야 한다면 상속해라.
  • 클래스의 객체가 단순히 다른 클래스의 객체를 사용만 해야한다면 위임을 사용해라.

자세한 설명은 준비님 블로그에 잘 정리되어있고, 내가 상속과 위임에 대해 가장 쉽게 이해했던 사례만 아래에 적어뒀다.

만약 “개는 동물이다”와 같이 포함 관계(is-a)에 있는 다른 클래스로 대체할 때는 상속, Manager가 Worker를 가지고 있고(has-a) Worker에게 일을 넘기는 것처럼 그저 다른 객체의 구현을 재사용하는 경우라면 위임을 사용하는게 좋다.

이미지 출처: https://typealias.com/start/kotlin-delegation/

위임 구현해보기(Delegation Interface)

일반적인 방법으로 인터페이스-클래스를 구성하면…

2개의 인터페이스가 있다고 가정해보자

interface Interface1 {
	fun func1()
}

interface Interface2 {
	fun func2()
}

그리고 이 두 인터페이스를 구현하는 클래스는 이렇게 작성될 것이고, 다이어그램으로 표현하면 아래와 같다.

class Class: Interface1, Interface2 {
	override fun func1() {
		println("func1")
	}

	override fun func2() {
		println("func2")
	}
}

fun main() {
	val obj = Class()
	obj.func1()
	obj.func2()
}

위임으로 구현하면

그렇다면 위임(Delegation)을 사용해서 구현하면 어떨까?

우선 다이어그램을 먼저 보자. 더이상 각 인터페이스를 Class에서 구현하지 않아도 된다. 즉 각 인터페이스의 구현을 인스턴스에 위임하게 된다.

func1과 func2의 구현이 Class에서 Interface1Impl와 Interface2Impl로 이동되었다. 이렇게하면 Class가 인터페이스 구현을 안해도 돼서 더 가벼워지게 된다.

interface Interface1 {
	fun func1()
}

interface Interface2 {
	fun func2()
}

class Interface1Impl: Interface1 {
	override fun func1() {
		println("func1")
	}
}

class Interface2Impl: Interface2 {
	override fun func2() {
		println("func2")
	}
}

class Class: Interface1 by Interface1Impl(), Interface2 by Interface2Impl() {

}

Class에서 인터페이스를 한번 더 재정의할 수도 있다

class  Class : Interface1  by  Interface1Imp (), Interface2 by Interface2Imp() {   
    override  fun  fun1 () {   
        println( "override fun1" )   
    }   
}

👨‍🍳 간단히 활용해보기

Delegation에 대해 귀여운 그림과 함께 설명해주는 아티클이 있어서 가져와봤다.

https://typealias.com/start/kotlin-delegation/

 

Introduction to Class Delegation

Use Kotlin’s class delegation feature to forward function calls from one object to another - without all the usual boilerplate!

typealias.com

식당에서 음식을 주문하는 과정을 예시로 들었는데, 아티클 전체를 정리하긴 넘 많고.. 그 상황을 참고해서 위임 패턴을 한 번 더 연습해보려고 한다.

enum class Entree { TOSSED_SALAD, SALMON_ON_RICE }
enum class Beverage { WATER, SODA }

interface KitchenStaff{
    fun prepareEntree(name: String): Entree?
    fun prepareBeverage(name: String): Beverage?
}

class Chef: KitchenStaff {
    override fun prepareEntree(name: String): Entree? {
        return when(name) {
            "Tossed Salad" -> Entree.TOSSED_SALAD
            "Salmon on Rice" -> Entree.SALMON_ON_RICE
            else -> null
        }
    }

    override fun prepareBeverage(name: String): Beverage? {
        return when(name) {
            "Water" -> Beverage.WATER
            "Soda" -> Beverage.SODA
            else -> null
        }
    }
}

class Waiter: KitchenStaff by Chef()

class DelegationTest() {
    @Test
    fun `웨이터가 일을 잘 시키는지 테스트`() {
        val waiter = Waiter()

        // The customer ordered Water
        val beverage = waiter.prepareBeverage("Water")
        assertEquals(beverage, Beverage.WATER)

        // The customer ordered Tossed Salad
        val entree = waiter.prepareEntree("Tossed Salad")
        assertEquals(entree, Entree.TOSSED_SALAD)
    }
}

 

...

오늘은 여기까지하고 by lazy {} , by remember {} 등이 구현되는 방식.. Delegated Properties에 대해서는 다음에 이어서 알아봐야겠당 >_o

 

'Kotlin' 카테고리의 다른 글

Kotlinx Serialization의 JsonBuilder Properties 알아보기  (0) 2023.02.23