객체를 정의하고 생성하는 방법을 지정할때 사용하는 가장 기본적인 방법은 기본 생성자를 사용하는 것이다.
class User(var name: String, var surname: String)
val user = User("Marcin", "Moskala")
기본 생성자가 좋은 방식인 이유를 이해하려면, 생성자와 관련된 아래 자바 패턴들을 이해하는 것이 좋다.
- 점층적 생성자 패턴(telescoping constructor pattern)
- 빌더 패턴(builder pattern)
점층적 생성자 패턴(telescoping constructor pattern)
여러가지 종류의 생성자를 사용하는 굉장히 간단한 패턴이다.
class Pizza {
val size: String
val cheese: Int
val olives: Int
val bacon: Int
constructor(size: String, cheess: Int, olives: Int, bacon: Int) {
this.size = size
this.cheess = cheess
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)
}
코틀린에서는 디폴트 아규먼트(default argument)를 사용할 수 있기 때문에 좋은 코드가 아니다. 일반적으로 아래와 같이 디폴트 아규먼트를 사용한다.
class Pizza(
val size: String,
val cheese: Int = 0,
val olives: Int = 0,
val bacon: Int = 0
)
디폴트 아규먼트가 점층적 생성자보다 좋은 이유는 다음과 같다
- 파라미터들의 값을 원하는 대로 지정할 수 있다.
- 아규먼트를 원하는 순서로 지정할 수 있다.
- 명시적으로 이름을 붙여서 아규먼트를 지정하므로 의미가 훨씬 명확하다.
빌더 패턴(builder pattern)
자바에서는 네임드 파라미터(named parameter)와 디폴트 아규먼트(default argument)를 사용할 수 없다. 자바에서는 빌더 패턴을 사용한다.
자바의 빌더 패턴은 아래와 같은 장점을 제공한다.
- 파라미터에 이름을 붙일 수 있다.
- 파라미터를 원하는 순서대로 지정할 수 있다.
- 디폴트 값을 지정할 수 있다.
class Pizza private constructor(
val size: String,
val cheese: Int,
val olives: Int,
val bacon: Int
) {
class Builder(private val size: String) {
private var cheese: Int = 0
private var olives: Int = 0
private var bacon: Int = 0
fun setCheese(value: Int): Builder = apply {
cheese = value
}
// ...
fun build() = Pizza(size, cheese, olives, bacon)
}
}
이런 모든 장점들은 코틀린의 디폴트 아규먼트와 네임드 파라미터도 가지고 있다.
빌더 패턴을 사용하는것 보다 네임드 파라미터를 사용하는게 좋은 이유는 아래와 같다.
- 더 짧다
- 더 명확하다
- 더 사용하기 쉽다
- 동시성과 관련된 문제가 없다: 코틀린의 함수 파라미터는 항상 immutable이지만 빌더 패턴의 프로퍼티는 mutable이다
값의 의미를 묶어서 지정해야 하는 경우나 특정 값을 누적하는 형태로 사용해야 하면 빌더 패턴을 이용하는게 나을 수도 있다.
val router = Router.Builder()
.addRoute(path = "/home", ::showHome)
.addRoute(path = "/users", ::showUsers)
.build()
하지만 코틀린에서는 DSL 빌더를 이용해 아래와 같이 구현할 수 있으므로 가급적이면 DSL을 이용하는것이 좋다.
val route = router {
"/home" directsTo ::showHome
"/users" directsTo ::showUsers
}
빌더 패턴은 다음과 같은 경우에만 사용하는것이 좋다.
- 빌더 패턴을 사용하는 다른 언어로 작성된 라이브러리를 그대로 옮길 때
- 디폴트 아규먼트와 DSL을 지원하지 않는 다른 언어에서 쉽게 사용할 수 있게 API를 설계할 때
위 두가지를 제외하면 빌더 패턴 대신에 디폴트 아규먼트를 갖는 기본 생성자 또는 DSL을 사용하는 것이 좋다.
정리
코틀린에서는 점층적 생성자 패턴과 빌더 패턴을 거의 사용하지 않는다.
디폴트 아규먼트나 기본 생성자, DSL을 활용하자.
Reference
- 이펙티브 코틀린 - 프로그래밍 인사이트, 마르친 모스칼라 지음, 윤인성 옮김
개인적인 기록을 위해 작성된 글이라 잘못된 내용이 있을 수 있습니다.
오류가 있다면 댓글을 남겨주세요.