반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item34. 기본 생성자에 이름 있는 옵션 아규먼트를 사용하라 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item34. 기본 생성자에 이름 있는 옵션 아규먼트를 사용하라

어리둥절범고래 2023. 12. 24. 16:56
반응형

객체를 정의하고 생성하는 가장 기본적인 방법은 기본 생성자(primary constructor)를 사용하는 것입니다.

class user(var name: String, var surname: String)
val user = User("Marcin", "Moskata")

 

기본 생성자로 객체를 만들 때는 객체의 초기상태를 나타내는 아규먼트를 전달합니다.

이러한 객체는 생성자로 상태를 초기화한 뒤, 그 프로퍼티를 유지합니다.

data class Student(
    val name: String,
    val surname: String,
    val age: Int
)

 

 

 

점층적 생성자 패턴

 

점층적 생성자 패턴은 '여러 가지 종류의 생성자를 사용하는' 간단한 패턴입니다.

class Pizza{
    val size: String
    val cheese: Int
    val olives: Int
    val bacon: Int

    constructor(size: String, cheese: Int, olives: Int, bacon: Int){
        this.size = size
        this.cheese = cheese
        this.olives = olives
        this.bacon = bacon
    }

    constructor(size: String, cheese: Int, olives: Int) : this(size, cheese, olives, 0)
    constructor(size: String, cheese: Int): this(size, cheese, 0)
    constructor(size: String): this(size, 0)   
}

코틀린에서는 일반적으로 디폴트 아규먼트를 사용할 수도 있습니다.

class Pizza(
    val size: String,
    val cheese: Int = 0,
    val olives: Int = 0,
    val bacon: Int = 0
)

디폴트 아규먼트가 점층적 생성자보다 좋은 이유는 다음과 같습니다.

 

  • 파라미터들의 값을 원하는 대로 지정할 수 있다.
  • 아규먼트를 원하는 순서로 지정할 수 있다.
  • 명시적으로 이름을 붙여서 아규먼트를 지정하므로 의미가 훨씬 명확하다.

 

마지막 이유가 가장 중요하며, 이름 있는 아규먼트를 활용해서 명시적으로 이름을 붙여 주면, 의미가 훨씬 명확해집니다.

val villagePizza = Pizza("L", 1, 2, 3)
val villagePizza = Pizza(
    size = "L",
    cheese = 1,
    olives = 2,
    bacon = 3
)

 

 

 

 

빌더 패턴

 

자바에서는 이름 있는 파라미터와 디폴트 아규먼트를 사용할 수 없으므로, 빌더 패턴을 사용합니다.

빌더 패턴을 사용하면, 다음과 같은 장점이 있습니다.

 

  • 파라미터에 이름을 붙일 수 있다.
  • 파라미터를 원하는 순서로 지정할 수 있다.
  • 디폴트 값을 지정할 수 있다.

 

빌더 패턴의 예를 코틀린으로 만들어 보면, 다음과 같습니다.

class Pizza private constructor(
    val size: String,
    val cheese: Int = 0,
    val olive: Int = 0,
    val bacon: Int = 0
) {
    class Builder(private val size: String){
        private var cheese: Int = 0
        private var olive: Int = 0
        private var bacon: Int = 0
        
        fun setCheese(value: Int): Builder = apply{
            cheese = value
        }

        fun setOlive(value: Int): Builder = apply{
            olive = value
        }

        fun setBacon(value: Int): Builder = apply{
            bacon = value
        }
        
        fun build() = Pizza(size, cheese, olive, bacon)
    }
}

빌더 패턴을 활용하면, 다음과 같이 파라미터에 이름을 붙여서 지정할 수 있습니다.

val myFavorite = Pizza.Builder("L").setOlives(3).build()

val villagePizza = Pizza.Builder("L")
    .setCheese(1)
    .setOlive(2)
    .setBacon(3)
    .build()

 

 

이러한 빌더 패턴은 코틀린의 디폴트 아규먼트와 이름 있는 파라미터로 구현 가능하며, 빌더 패턴을 사용하는 것 보다 이름 있는 파라미터를 사용하는 것이 좋은 이유는 다음과 같습니다.

 

  • 더 짧다.
  • 더 명확하다.
  • 더 사용하기 쉽다.
  • 동시성과 관련된 문제가 없다: 코틀린의 함수 파라미터는 항상 immutable이지만, 대부분의 빌더 패턴에서 프로퍼티는 mutable이다. 따라서 빌더 패턴의 빌더 함수를 쓰레드 안전하게 구현하는 것은 어렵다.

 

물론, 빌더 패턴이 좋은 경우도 있습니다.

빌더 패턴은 값의 의미를 묶어서 지정할 수 있으며, 특정 값을 누적하는 형태로 사용할 수 있습니다.

// 값의 의미를 묶어서 지정
val dialog = AlertDialog.Builder(context)
    .setMessage(R.string.fire_missiles)
    .setFireButton(R.string.fire, {id -> /*..*/})
    .setCancelButton(R.string.cancel, {id -> /*..*/})
    .create()

// 특정 값을 누적
val router = Router.Builder()
    .addRoute(path= "/home", ::showHome)
    .addRoute(path= "/users", ::showUsers)
    .build()

빌더 패턴을 사용하지 않고 이를 구현하려면, 추가적인 타입들을 만들고 활용해야 하기 때문에 코드가 오히려 복잡해 질 수 있습니다.

val dialog = AlertDialog(context,
    message = R.string.fire_missiles,
    positiveButtonDescription = 
        ButtonDescription(R.string.fire, { d, id ->
            // 미사일 발사!
        }),
    NegativeButtonDiscription = 
        ButtonDiscription(R.string.cancle, { d, id ->
            // 사용자가 대화상자에서 취소를 누른 경우
        })
)

val router = Router(
    router = listOf(
        Route("/home", ::showHome),
        Route("/users", ::showUsers)
    )
)

 

이런 코드는 DSL(Domain Specific Language) 빌더를 사용하면 더 유연하고 명확한 형태의 코드를 작성할 수 있습니다.

var dialog = context.alert(R.string.fire_missiles) {
    positiveButton(R.string.fire) {
        // 미사일 발사!
    }
    nagativeButton(R.string.cancel) {
        // 사용자가 대화상자에서 취소를 누른 경우
    }
}

val route = router {
    "/home" directTo :: showHome
    "/users" directTo :: showUsers
}

 

 

결론적으로 코틀린에서는 빌더 패턴을 거의 사용하지 않으며, 빌더 패턴은 다음과 같은 경우에만 사용합니다.

 

  • 빌더 패턴을 사용하는 다른 언어로 작성된 라이브러리를 그대로 옮길 때
  • 디폴트 아규먼트와 DSL을 지원하지 않는 다른 언어에서 쉽게 사용할 수 있게 API를 설계할 때

 

 


정리

 

  • 일반적으로 기본 생성자를 사용해 객체를 만든다.
  • 코틀린에서는 점층적 생성자 패턴을 사용하지 않는다.
  • 디폴트 아규먼트를 사용하는 것이 좋다.
  • 빌더 패턴도 마찬가지로 거의 사용하지 않는다.
  • 빌더 패턴 대신 기본 생성자를 사용하는 코드로 바꾸거나, DSL을 활용하라.
반응형